#include "ApplicationManager.h"
#include "../state/StateManager.h"
#include "../input/InputManager.h"
#include "../interfaces/IAudioSystem.h"
#include "../interfaces/IRenderer.h"
#include "../interfaces/IAssetLoader.h"
#include "../interfaces/IInputHandler.h"
#include <filesystem>
#include "../../audio/Audio.h"
#include "../../audio/AudioManager.h"
#include "../../audio/SoundEffect.h"
#include "../../persistence/Scores.h"
#include "../../states/State.h"
#include "../../states/LoadingState.h"
#include "../../states/MenuState.h"
#include "../../states/OptionsState.h"
#include "../../states/LevelSelectorState.h"
#include "../../states/PlayingState.h"
#include "../assets/AssetManager.h"
#include "../Config.h"
#include "../GlobalState.h"
#include "../../graphics/renderers/RenderManager.h"
#include "../../graphics/ui/Font.h"
#include "../../graphics/ui/HelpOverlay.h"
#include "../../graphics/effects/Starfield3D.h"
#include "../../graphics/effects/Starfield.h"
#include "../../graphics/renderers/GameRenderer.h"
#include "../../gameplay/core/Game.h"
#include "../../gameplay/coop/CoopGame.h"
#include "../../gameplay/effects/LineEffect.h"
#include <SDL3/SDL.h>
#include <SDL3_image/SDL_image.h>
#include <SDL3_ttf/SDL_ttf.h>
#include "../../utils/ImagePathResolver.h"
#include <iostream>
#include "../../video/VideoPlayer.h"
#include <cmath>
#include <fstream>
#include <algorithm>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#include <shellapi.h>
#endif
// (Intro video playback is now handled in-process via VideoPlayer)

ApplicationManager::ApplicationManager() = default;

static void traceFile(const char* msg) {
    std::ofstream f("spacetris_trace.log", std::ios::app);
    if (f) f << msg << "\n";
}

// Helper: extracted from inline lambda to avoid MSVC parsing issues with complex lambdas
void ApplicationManager::renderLoading(ApplicationManager* app, RenderManager& renderer) {
    // Clear background first
    renderer.clear(0, 0, 0, 255);

    // Use 3D starfield for loading screen (full screen)
    if (app->m_starfield3D) {
        int winW_actual = 0, winH_actual = 0;
        if (app->m_renderManager) app->m_renderManager->getWindowSize(winW_actual, winH_actual);
        if (winW_actual > 0 && winH_actual > 0) app->m_starfield3D->resize(winW_actual, winH_actual);
        app->m_starfield3D->draw(renderer.getSDLRenderer());
    }
    // If intro video is playing, render it instead of the loading UI
    if (app->m_introStarted && app->m_videoPlayer) {
        SDL_Renderer* sdlR = renderer.getSDLRenderer();
        int winW=0, winH=0; renderer.getWindowSize(winW, winH);
        app->m_videoPlayer->render(sdlR, winW, winH);
        SDL_SetRenderViewport(renderer.getSDLRenderer(), nullptr);
        SDL_SetRenderScale(renderer.getSDLRenderer(), 1.0f, 1.0f);
        return;
    }
    SDL_Rect logicalVP = {0,0,0,0};
    float logicalScale = 1.0f;
    if (app->m_renderManager) {
        logicalVP = app->m_renderManager->getLogicalViewport();
        logicalScale = app->m_renderManager->getLogicalScale();
    }
    SDL_SetRenderViewport(renderer.getSDLRenderer(), &logicalVP);
    SDL_SetRenderScale(renderer.getSDLRenderer(), logicalScale, logicalScale);

    float contentOffsetX = 0.0f;
    float contentOffsetY = 0.0f;

    auto drawRectOriginal = [&](float x, float y, float w, float h, SDL_Color c) {
        SDL_SetRenderDrawColor(renderer.getSDLRenderer(), c.r, c.g, c.b, c.a);
        SDL_FRect fr;
        fr.x = x + contentOffsetX;
        fr.y = y + contentOffsetY;
        fr.w = w;
        fr.h = h;
        SDL_RenderFillRect(renderer.getSDLRenderer(), &fr);
    };

    // Compute dynamic logical width/height based on the RenderManager's
    // computed viewport and scale so the loading UI sizes itself to the
    // actual content area rather than a hardcoded design size.
    float LOGICAL_W = static_cast<float>(Config::Logical::WIDTH);
    float LOGICAL_H = static_cast<float>(Config::Logical::HEIGHT);
    if (logicalScale > 0.0f && logicalVP.w > 0 && logicalVP.h > 0) {
        // logicalVP is in window pixels; divide by scale to get logical units
        LOGICAL_W = static_cast<float>(logicalVP.w) / logicalScale;
        LOGICAL_H = static_cast<float>(logicalVP.h) / logicalScale;
    }
    const bool isLimitedHeight = LOGICAL_H < 450.0f;
    SDL_Texture* logoTex = app->m_assetManager->getTexture("logo");
    const float logoHeight = logoTex ? (isLimitedHeight ? LOGICAL_H * 0.25f : LOGICAL_H * 0.4f) : 0;
    const float loadingTextHeight = 20;
    const float barHeight = 20;
    const float barPaddingVertical = isLimitedHeight ? 15 : 35;
    const float percentTextHeight = 24;
    const float spacingBetweenElements = isLimitedHeight ? 5 : 15;

    const float totalContentHeight = logoHeight + (logoHeight > 0 ? spacingBetweenElements : 0) + loadingTextHeight + barPaddingVertical + barHeight + spacingBetweenElements + percentTextHeight;

    float currentY = (LOGICAL_H - totalContentHeight) / 2.0f;

    if (logoTex) {
        const int lw = 872, lh = 273;
    const float maxLogoWidth = std::min(LOGICAL_W * 0.9f, 600.0f);
        const float availableHeight = isLimitedHeight ? LOGICAL_H * 0.25f : LOGICAL_H * 0.4f;
        const float availableWidth = maxLogoWidth;
        const float scaleFactorWidth = availableWidth / static_cast<float>(lw);
        const float scaleFactorHeight = availableHeight / static_cast<float>(lh);
        const float scaleFactor = std::min(scaleFactorWidth, scaleFactorHeight);
        const float displayWidth = lw * scaleFactor;
        const float displayHeight = lh * scaleFactor;
        const float logoX = (LOGICAL_W - displayWidth) / 2.0f;
        SDL_FRect dst{logoX + contentOffsetX, currentY + contentOffsetY, displayWidth, displayHeight};
        SDL_RenderTexture(renderer.getSDLRenderer(), logoTex, nullptr, &dst);
        currentY += displayHeight + spacingBetweenElements;
    }

    FontAtlas* pixelFont = (FontAtlas*)app->m_assetManager->getFont("pixel_font");
    FontAtlas* fallbackFont = (FontAtlas*)app->m_assetManager->getFont("main_font");
    FontAtlas* loadingFont = pixelFont ? pixelFont : fallbackFont;
    if (loadingFont) {
        const std::string loadingText = "LOADING";
        int tW=0, tH=0; loadingFont->measure(loadingText, 1.0f, tW, tH);
        float textX = (LOGICAL_W - (float)tW) * 0.5f;
        loadingFont->draw(renderer.getSDLRenderer(), textX + contentOffsetX, currentY + contentOffsetY, loadingText, 1.0f, {255,204,0,255});
    }

    currentY += loadingTextHeight + barPaddingVertical;

    const int barW = 400, barH = 20;
    const int bx = (LOGICAL_W - barW) / 2;
    float loadingProgress = app->m_assetManager->getLoadingProgress();
    drawRectOriginal(bx - 3, currentY - 3, barW + 6, barH + 6, {68,68,80,255});
    drawRectOriginal(bx, currentY, barW, barH, {34,34,34,255});
    drawRectOriginal(bx, currentY, int(barW * loadingProgress), barH, {255,204,0,255});
    currentY += barH + spacingBetweenElements;

    if (loadingFont) {
        int percentage = int(loadingProgress * 100);
        char percentText[16];
        std::snprintf(percentText, sizeof(percentText), "%d%%", percentage);
        std::string pStr(percentText);
        int pW=0, pH=0; loadingFont->measure(pStr, 1.5f, pW, pH);
        float percentX = (LOGICAL_W - (float)pW) * 0.5f;
        loadingFont->draw(renderer.getSDLRenderer(), percentX + contentOffsetX, currentY + contentOffsetY, pStr, 1.5f, {255,204,0,255});
    }

    SDL_SetRenderViewport(renderer.getSDLRenderer(), nullptr);
    SDL_SetRenderScale(renderer.getSDLRenderer(), 1.0f, 1.0f);
}


ApplicationManager::~ApplicationManager() {
    if (m_initialized) {
        shutdown();
    }
}

bool ApplicationManager::initialize(int argc, char* argv[]) {
    if (m_initialized) {
        SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "ApplicationManager already initialized");
        return true;
    }

    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Initializing ApplicationManager...");

    // Initialize GlobalState
    GlobalState::instance().initialize();
    
    // Set initial logical dimensions
    GlobalState::instance().updateLogicalDimensions(m_windowWidth, m_windowHeight);

    // Initialize SDL first
    if (!initializeSDL()) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to initialize SDL");
        return false;
    }

    // Initialize managers
    if (!initializeManagers()) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to initialize managers");
        cleanupSDL();
        return false;
    }

    // Register services for dependency injection
    registerServices();

    // Initialize game systems
    if (!initializeGame()) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to initialize game systems");
        cleanupManagers();
        cleanupSDL();
        return false;
    }

    m_initialized = true;
    m_running = true;
    m_lastFrameTime = SDL_GetTicks();

    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "ApplicationManager initialized successfully");
    return true;
}

void ApplicationManager::run() {
    if (!m_initialized) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "ApplicationManager not initialized");
        return;
    }

    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Starting main application loop");
    traceFile("Main loop starting");

    while (m_running) {
    SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "Main loop iteration start: m_running=%d", m_running ? 1 : 0);
    traceFile("Main loop iteration");
        // Calculate delta time
        uint64_t currentTime = SDL_GetTicks();
        float deltaTime = (currentTime - m_lastFrameTime) / 1000.0f;
        m_lastFrameTime = currentTime;

        // Limit delta time to prevent spiral of death
        if (deltaTime > Config::Performance::MIN_FRAME_TIME) {
            deltaTime = Config::Performance::MIN_FRAME_TIME;
        }

        // Main loop phases
        processEvents();
        
        if (m_running) {
            update(deltaTime);
            traceFile("about to call render");
            render();
        }
    }

    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Main application loop ended");
}

void ApplicationManager::shutdown() {
    if (!m_initialized) {
        return;
    }

    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Shutting down ApplicationManager...");

    m_running = false;

    // Stop audio systems before tearing down SDL to avoid aborts/asserts
    if (auto sys = AudioManager::get()) sys->shutdown();
    SoundEffectManager::instance().shutdown();

    // Cleanup in reverse order of initialization
    cleanupManagers();
    cleanupSDL();
    
    // Shutdown GlobalState last
    GlobalState::instance().shutdown();

    m_initialized = false;
    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "ApplicationManager shutdown complete");
}

bool ApplicationManager::initializeSDL() {
    // Initialize SDL subsystems
    int sdlResult = SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
    if (sdlResult < 0) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "SDL_Init failed: %s", SDL_GetError());
        return false;
    }

    // Initialize SDL_ttf
    int ttfResult = TTF_Init();
    if (ttfResult < 0) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "TTF_Init failed: %s", SDL_GetError());
        SDL_Quit();
        return false;
    }

    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "SDL initialized successfully");
    return true;
}

bool ApplicationManager::initializeManagers() {
    // Create and initialize RenderManager
    m_renderManager = std::make_unique<RenderManager>();
    if (!m_renderManager->initialize(m_windowWidth, m_windowHeight, m_windowTitle)) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to initialize RenderManager");
        return false;
    }
    m_isFullscreen = m_renderManager->isFullscreen();

    // Create InputManager
    m_inputManager = std::make_unique<InputManager>();
    if (!m_inputManager) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to create InputManager");
        return false;
    }

    // Create and initialize AssetManager
    m_assetManager = std::make_unique<AssetManager>();
    if (!m_assetManager->initialize(m_renderManager->getSDLRenderer())) {
        SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, "Failed to initialize AssetManager");
        return false;
    }

    // Ensure SoundEffectManager is initialized early so SFX loads work
    SoundEffectManager::instance().init();

    // Create StateManager (will be enhanced in next steps)
    m_stateManager = std::make_unique<StateManager>(AppState::Loading);

    // Create and initialize starfields
    m_starfield3D = std::make_unique<Starfield3D>();
    m_starfield3D->init(Config::Logical::WIDTH, Config::Logical::HEIGHT, 200);
    
    m_starfield = std::make_unique<Starfield>();
    m_starfield->init(Config::Logical::WIDTH, Config::Logical::HEIGHT, 50);

    // Register InputManager handlers to forward events to StateManager so
    // state-specific event handlers receive SDL_Event objects just like main.cpp.
    if (m_inputManager && m_stateManager) {
        m_inputManager->registerKeyHandler([this](SDL_Scancode sc, bool pressed){
            if (!m_stateManager) return;

            bool consume = false;

            // Global hotkeys (handled across all states)
            if (pressed) {
                // While the help overlay is visible, swallow input so gameplay/menu doesn't react.
                // Allow only help-toggle/close keys to pass through this global handler.
                if (m_showHelpOverlay) {
                    if (sc == SDL_SCANCODE_ESCAPE) {
                        m_showHelpOverlay = false;
                        if (m_helpOverlayPausedGame && m_game) {
                            m_game->setPaused(false);
                        }
                        m_helpOverlayPausedGame = false;
                    } else if (sc == SDL_SCANCODE_F1) {
                        // Toggle off
                        m_showHelpOverlay = false;
                        if (m_helpOverlayPausedGame && m_game) {
                            m_game->setPaused(false);
                        }
                        m_helpOverlayPausedGame = false;
                    }
                    consume = true;
                }

                // Toggle fullscreen on F, F11 or Alt+Enter (or Alt+KP_Enter)
                if (sc == SDL_SCANCODE_F || sc == SDL_SCANCODE_F11 ||
                    ((sc == SDL_SCANCODE_RETURN || sc == SDL_SCANCODE_RETURN2 || sc == SDL_SCANCODE_KP_ENTER) &&
                     (SDL_GetModState() & SDL_KMOD_ALT))) {
                    if (m_renderManager) {
                        bool fs = m_renderManager->isFullscreen();
                        m_renderManager->setFullscreen(!fs);
                        m_isFullscreen = m_renderManager->isFullscreen();
                    }
                    consume = true;
                }

                // M: Toggle/mute music; start playback if unmuting and not started yet
                if (!consume && sc == SDL_SCANCODE_M) {
                    if (auto sys = AudioManager::get()) sys->toggleMute();
                    m_musicEnabled = !m_musicEnabled;
                    if (m_musicEnabled && !m_musicStarted && AudioManager::get() && AudioManager::get()->getLoadedTrackCount() > 0) {
                        AudioManager::get()->shuffle();
                        AudioManager::get()->start();
                        m_musicStarted = true;
                    }
                    consume = true;
                }

                // N: Skip to next song in the playlist (or restart menu track)
                if (!consume && sc == SDL_SCANCODE_N) {
                    if (auto sys = AudioManager::get()) { sys->skipToNextTrack(); if (!m_musicStarted && sys->getLoadedTrackCount() > 0) { m_musicStarted = true; m_musicEnabled = true; } }
                    consume = true;
                }

                if (!consume && (sc == SDL_SCANCODE_F1)) {
                    AppState currentState = m_stateManager ? m_stateManager->getState() : AppState::Loading;
                    // F1 is global (except Loading).
                    if (currentState != AppState::Loading) {
                        m_showHelpOverlay = !m_showHelpOverlay;
                        if (currentState == AppState::Playing && m_game) {
                        if (m_showHelpOverlay) {
                            if (!m_game->isPaused()) {
                                m_game->setPaused(true);
                                m_helpOverlayPausedGame = true;
                            } else {
                                m_helpOverlayPausedGame = false;
                            }
                        } else if (m_helpOverlayPausedGame) {
                            m_game->setPaused(false);
                            m_helpOverlayPausedGame = false;
                        }
                        } else if (!m_showHelpOverlay) {
                            m_helpOverlayPausedGame = false;
                        }
                    }
                    consume = true;
                }
            }

            // Forward to current state unless consumed
            if (!consume) {
                SDL_Event ev{};
                ev.type = pressed ? SDL_EVENT_KEY_DOWN : SDL_EVENT_KEY_UP;
                ev.key.scancode = sc;
                ev.key.repeat = 0;
                m_stateManager->handleEvent(ev);
            }
        });

        m_inputManager->registerMouseButtonHandler([this](int button, bool pressed, float x, float y){
            if (!m_stateManager) return;
            if (m_showHelpOverlay) return;
            SDL_Event ev{};
            ev.type = pressed ? SDL_EVENT_MOUSE_BUTTON_DOWN : SDL_EVENT_MOUSE_BUTTON_UP;
            ev.button.button = button;
            ev.button.x = int(x);
            ev.button.y = int(y);
            m_stateManager->handleEvent(ev);
        });

        m_inputManager->registerMouseMotionHandler([this](float x, float y, float dx, float dy){
            if (!m_stateManager) return;
            if (m_showHelpOverlay) return;
            SDL_Event ev{};
            ev.type = SDL_EVENT_MOUSE_MOTION;
            ev.motion.x = int(x);
            ev.motion.y = int(y);
            ev.motion.xrel = int(dx);
            ev.motion.yrel = int(dy);
            m_stateManager->handleEvent(ev);
        });

        m_inputManager->registerWindowEventHandler([this](const SDL_WindowEvent& we){
            // Handle window resize events for RenderManager
            if (we.type == SDL_EVENT_WINDOW_RESIZED && m_renderManager) {
                m_renderManager->handleWindowResize(we.data1, we.data2);
                
                // Update GlobalState logical dimensions when window resizes
                GlobalState::instance().updateLogicalDimensions(we.data1, we.data2);
            }
            
            // Forward all window events to StateManager
            if (!m_stateManager) return;
            SDL_Event ev{};
            ev.type = we.type;
            ev.window = we;
            m_stateManager->handleEvent(ev);
        });

        m_inputManager->registerQuitHandler([this](){
            SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "[QUIT] InputManager quit handler invoked - setting running=false");
            traceFile("ApplicationManager: quit handler -> m_running=false");
            m_running = false;
        });
    }

    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Managers initialized successfully");
    return true;
}

void ApplicationManager::registerServices() {
    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Registering services for dependency injection...");
    
    // Register concrete implementations as interface singletons
    if (m_renderManager) {
        std::shared_ptr<RenderManager> renderPtr(m_renderManager.get(), [](RenderManager*) {
            // Custom deleter that does nothing since the unique_ptr manages lifetime
        });
        m_serviceContainer.registerSingleton<IRenderer>(renderPtr);
        SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Registered IRenderer service");
    }
    
    if (m_assetManager) {
        std::shared_ptr<AssetManager> assetPtr(m_assetManager.get(), [](AssetManager*) {
            // Custom deleter that does nothing since the unique_ptr manages lifetime
        });
        m_serviceContainer.registerSingleton<IAssetLoader>(assetPtr);
        SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Registered IAssetLoader service");
    }
    
    if (m_inputManager) {
        std::shared_ptr<InputManager> inputPtr(m_inputManager.get(), [](InputManager*) {
            // Custom deleter that does nothing since the unique_ptr manages lifetime
        });
        m_serviceContainer.registerSingleton<IInputHandler>(inputPtr);
        SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Registered IInputHandler service");
    }
    
    // Register Audio system singleton (via AudioManager)
    IAudioSystem* audioInstance = AudioManager::get();
    if (audioInstance) {
        std::shared_ptr<IAudioSystem> audioPtr(audioInstance, [](IAudioSystem*){});
        m_serviceContainer.registerSingleton<IAudioSystem>(audioPtr);
        SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Registered IAudioSystem service");
    }
    
    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Service registration completed successfully");
}

bool ApplicationManager::initializeGame() {
    // Load essential assets using AssetManager
    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Loading essential assets...");
    
    // Set up asset loading tasks
    AssetManager::LoadingTask logoTask{AssetManager::LoadingTask::TEXTURE, "logo", Config::Assets::LOGO_BMP};
    AssetManager::LoadingTask backgroundTask{AssetManager::LoadingTask::TEXTURE, "background", Config::Assets::BACKGROUND_BMP};
    AssetManager::LoadingTask blocksTask{AssetManager::LoadingTask::TEXTURE, "blocks", Config::Assets::BLOCKS_BMP};
    AssetManager::LoadingTask fontTask{AssetManager::LoadingTask::FONT, "main_font", Config::Fonts::DEFAULT_FONT_PATH, Config::Fonts::DEFAULT_FONT_SIZE};
    AssetManager::LoadingTask pixelFontTask{AssetManager::LoadingTask::FONT, "pixel_font", Config::Fonts::PIXEL_FONT_PATH, Config::Fonts::PIXEL_FONT_SIZE};

    // Pre-load the pixel (retro) font synchronously so the loading screen can render text immediately
    if (!m_assetManager->getFont("pixel_font")) {
        if (m_assetManager->loadFont("pixel_font", Config::Fonts::PIXEL_FONT_PATH, Config::Fonts::PIXEL_FONT_SIZE)) {
            SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Preloaded pixel_font for loading screen");
        } else {
            SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Failed to preload pixel_font; loading screen will fallback to main_font");
        }
    }

    // Add tasks to AssetManager (pixel font task will be skipped if already loaded)
    m_assetManager->addLoadingTask(logoTask);
    m_assetManager->addLoadingTask(backgroundTask);
    m_assetManager->addLoadingTask(blocksTask);
    m_assetManager->addLoadingTask(fontTask);
    m_assetManager->addLoadingTask(pixelFontTask);
    
    // Execute loading tasks with progress callback
    m_assetManager->executeLoadingTasks([](float progress) {
        SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Asset loading progress: %.1f%%", progress * 100.0f);
    });
    
    // Load sound effects with fallback (SoundEffectManager already initialized)
    m_assetManager->loadSoundEffectWithFallback("clear_line", "clear_line");
    m_assetManager->loadSoundEffectWithFallback("nice_combo", "nice_combo");
    m_assetManager->loadSoundEffectWithFallback("great_move", "great_move");
    m_assetManager->loadSoundEffectWithFallback("amazing", "amazing");
    m_assetManager->loadSoundEffectWithFallback("lets_go", "lets_go");
    
    // Start background music loading
    m_assetManager->startBackgroundMusicLoading();
    
    // Create and populate shared StateContext similar to main.cpp so states like MenuState
    // receive the same pointers and flags they expect.
    // Create ScoreManager and load scores
    m_scoreManager = std::make_unique<ScoreManager>();
    if (m_scoreManager) m_scoreManager->load();

    // Create gameplay and line effect objects to populate StateContext like main.cpp
    m_lineEffect = std::make_unique<LineEffect>();
    if (m_renderManager && m_renderManager->getSDLRenderer()) {
        m_lineEffect->init(m_renderManager->getSDLRenderer());
    }
    m_game = std::make_unique<Game>(m_startLevelSelection);
    m_coopGame = std::make_unique<CoopGame>(m_startLevelSelection);
    // Wire up sound callbacks as main.cpp did
    if (m_game) {
        // Apply global gravity speed multiplier from config
        m_game->setGravityGlobalMultiplier(Config::Gameplay::GRAVITY_SPEED_MULTIPLIER);
        // Reset to recalculate gravity with the new multiplier
        m_game->reset(m_startLevelSelection);
        
        m_game->setSoundCallback([&](int linesCleared){
            SoundEffectManager::instance().playSound("clear_line", 1.0f);
            // voice lines handled via asset manager loaded sounds
            if (linesCleared == 2) SoundEffectManager::instance().playRandomSound({"nice_combo"}, 1.0f);
            else if (linesCleared == 3) SoundEffectManager::instance().playRandomSound({"great_move"}, 1.0f);
            else if (linesCleared == 4) SoundEffectManager::instance().playRandomSound({"amazing"}, 1.0f);
        });
        m_game->setLevelUpCallback([&](int newLevel){
            SoundEffectManager::instance().playSound("lets_go", 1.0f);
        });
    }

    if (m_coopGame) {
        // TODO: tune gravity with Config and shared level scaling once coop rules are finalized
        m_coopGame->reset(m_startLevelSelection);
        // Wire coop sound callback to reuse same clear-line VO/SFX behavior
        m_coopGame->setSoundCallback([&](int linesCleared){
            SoundEffectManager::instance().playSound("clear_line", 1.0f);
            if (linesCleared == 2) SoundEffectManager::instance().playRandomSound({"nice_combo"}, 1.0f);
            else if (linesCleared == 3) SoundEffectManager::instance().playRandomSound({"great_move"}, 1.0f);
            else if (linesCleared == 4) SoundEffectManager::instance().playRandomSound({"amazing"}, 1.0f);
        });
    }

    // Prepare a StateContext-like struct by setting up handlers that capture
    // pointers and flags. State objects in this refactor expect these to be
    // available via StateManager event/update/render hooks, so we'll store them
    // as lambdas that reference members here.

    // Start background music loading similar to main.cpp: Audio init + file discovery
    if (auto sys = AudioManager::get()) sys->init();
    // Discover available tracks (up to 100) and queue for background loading
    m_totalTracks = 0;
    std::vector<std::string> trackPaths;
    trackPaths.reserve(100);
    for (int i = 1; i <= 100; ++i) {
        char base[128];
        std::snprintf(base, sizeof(base), "assets/music/music%03d", i);
        std::string path = AssetPath::resolveWithExtensions(base, { ".mp3" });
        if (path.empty()) {
            break;
        }
        trackPaths.push_back(path);
    }
    m_totalTracks = static_cast<int>(trackPaths.size());
    for (const auto& path : trackPaths) {
        if (auto sys = AudioManager::get()) sys->addTrackAsync(path);
    }
    if (m_totalTracks > 0) {
        if (auto sys = AudioManager::get()) sys->startBackgroundLoading();
        // Kick off playback now; Audio will pick a track once decoded.
        // Do not mark as started yet; we'll flip the flag once a track is actually loaded.
        if (m_musicEnabled) {
            if (auto sys = AudioManager::get()) { sys->shuffle(); sys->start(); }
            m_musicStarted = true;
        }
        m_currentTrackLoading = 1; // mark started
    }

    // Instantiate state objects and populate a StateContext similar to main.cpp
    // so that existing state classes (MenuState, LoadingState, etc.) receive
    // the resources they expect.
    {
        m_stateContext.stateManager = m_stateManager.get();
        m_stateContext.game = m_game.get();
        m_stateContext.coopGame = m_coopGame.get();
        m_stateContext.scores = m_scoreManager.get();
        m_stateContext.starfield = m_starfield.get();
        m_stateContext.starfield3D = m_starfield3D.get();
        m_stateContext.font = (FontAtlas*)m_assetManager->getFont("main_font");
        m_stateContext.pixelFont = (FontAtlas*)m_assetManager->getFont("pixel_font");
        m_stateContext.lineEffect = m_lineEffect.get();
        m_stateContext.logoTex = m_assetManager->getTexture("logo");
        // Attempt to load a small logo variant if present to match original UX
        SDL_Texture* logoSmall = m_assetManager->getTexture("logo_small");
        if (!logoSmall) {
            // Try to load image from disk and register with AssetManager
            if (m_assetManager->loadTexture("logo_small", "assets/images/logo_small.bmp")) {
                logoSmall = m_assetManager->getTexture("logo_small");
            }
        }
        m_stateContext.logoSmallTex = logoSmall;
        if (logoSmall) {
            int w = 0, h = 0; if (m_renderManager) m_renderManager->getTextureSize(logoSmall, w, h);
            m_stateContext.logoSmallW = w; m_stateContext.logoSmallH = h;
        } else { m_stateContext.logoSmallW = 0; m_stateContext.logoSmallH = 0; }
        m_stateContext.backgroundTex = m_assetManager->getTexture("background");
        m_stateContext.blocksTex = m_assetManager->getTexture("blocks");
        m_stateContext.asteroidsTex = m_assetManager->getTexture("asteroids");
        m_stateContext.musicEnabled = &m_musicEnabled;
        m_stateContext.musicStarted = &m_musicStarted;
        m_stateContext.musicLoaded = &m_musicLoaded;
        m_stateContext.startLevelSelection = &m_startLevelSelection;
        m_stateContext.hoveredButton = &m_hoveredButton;
        m_stateContext.showSettingsPopup = &m_showSettingsPopup;
        m_stateContext.showHelpOverlay = &m_showHelpOverlay;
        m_stateContext.showExitConfirmPopup = &m_showExitConfirmPopup;
        m_stateContext.exitPopupSelectedButton = &m_exitPopupSelectedButton;
        m_stateContext.playerName = &m_playerName;
        m_stateContext.fullscreenFlag = &m_isFullscreen;
        m_stateContext.applyFullscreen = [this](bool enable) {
            if (m_renderManager) {
                m_renderManager->setFullscreen(enable);
                m_isFullscreen = m_renderManager->isFullscreen();
            } else {
                m_isFullscreen = enable;
            }
        };
        m_stateContext.queryFullscreen = [this]() -> bool {
            if (m_renderManager) {
                return m_renderManager->isFullscreen();
            }
            return m_isFullscreen;
        };
        m_stateContext.requestQuit = [this]() {
            requestShutdown();
        };

        // Create state instances
        m_loadingState = std::make_unique<LoadingState>(m_stateContext);
        m_menuState = std::make_unique<MenuState>(m_stateContext);
        m_optionsState = std::make_unique<OptionsState>(m_stateContext);
        m_levelSelectorState = std::make_unique<LevelSelectorState>(m_stateContext);
        m_playingState = std::make_unique<PlayingState>(m_stateContext);

        // Register handlers that forward to these state objects
        if (m_stateManager) {
            m_stateManager->registerEventHandler(AppState::Loading, [this](const SDL_Event& e){ if (m_loadingState) m_loadingState->handleEvent(e); });
            m_stateManager->registerOnEnter(AppState::Loading, [this](){ if (m_loadingState) m_loadingState->onEnter(); });
            m_stateManager->registerOnExit(AppState::Loading, [this](){ if (m_loadingState) m_loadingState->onExit(); });

            m_stateManager->registerEventHandler(AppState::Menu, [this](const SDL_Event& e){ if (m_menuState) m_menuState->handleEvent(e); });
            m_stateManager->registerOnEnter(AppState::Menu, [this](){ if (m_menuState) m_menuState->onEnter(); });
            m_stateManager->registerOnExit(AppState::Menu, [this](){ if (m_menuState) m_menuState->onExit(); });

            m_stateManager->registerEventHandler(AppState::Options, [this](const SDL_Event& e){ if (m_optionsState) m_optionsState->handleEvent(e); });
            m_stateManager->registerOnEnter(AppState::Options, [this](){ if (m_optionsState) m_optionsState->onEnter(); });
            m_stateManager->registerOnExit(AppState::Options, [this](){ if (m_optionsState) m_optionsState->onExit(); });

            m_stateManager->registerEventHandler(AppState::LevelSelector, [this](const SDL_Event& e){ if (m_levelSelectorState) m_levelSelectorState->handleEvent(e); });
            m_stateManager->registerOnEnter(AppState::LevelSelector, [this](){ if (m_levelSelectorState) m_levelSelectorState->onEnter(); });
            m_stateManager->registerOnExit(AppState::LevelSelector, [this](){ if (m_levelSelectorState) m_levelSelectorState->onExit(); });

            m_stateManager->registerEventHandler(AppState::Playing, [this](const SDL_Event& e){ if (m_playingState) m_playingState->handleEvent(e); });
            m_stateManager->registerOnEnter(AppState::Playing, [this](){ if (m_playingState) m_playingState->onEnter(); });
            m_stateManager->registerOnExit(AppState::Playing, [this](){ if (m_playingState) m_playingState->onExit(); });
        }
    }

    // Finally call setupStateHandlers for inline visuals and additional hooks
    setupStateHandlers();

    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Game systems initialized with asset loading");
    return true;
}

void ApplicationManager::setupStateHandlers() {
    // Helper function for drawing rectangles
    auto drawRect = [](SDL_Renderer* renderer, float x, float y, float w, float h, uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
        SDL_SetRenderDrawColor(renderer, r, g, b, a);
        SDL_FRect rect = { x, y, w, h };
        SDL_RenderFillRect(renderer, &rect);
    };

    // Helper function for drawing menu buttons with enhanced styling
    auto drawEnhancedButton = [drawRect](SDL_Renderer* renderer, FontAtlas* font, float cx, float cy, float w, float h, 
                                        const std::string& label, bool isHovered, bool isSelected) {
        float x = cx - w/2;
        float y = cy - h/2;
        
        // Button styling based on state
        SDL_Color bgColor = isSelected ? SDL_Color{100, 150, 255, 255} : 
                           isHovered ? SDL_Color{80, 120, 200, 255} : 
                           SDL_Color{60, 90, 160, 255};
        
        // Draw border and background
        drawRect(renderer, x-2, y-2, w+4, h+4, 60, 80, 140, 255); // Border
        drawRect(renderer, x, y, w, h, bgColor.r, bgColor.g, bgColor.b, bgColor.a); // Background
        
        // Draw text if font is available
        if (font) {
            float textScale = 1.8f;
            float approxCharW = 12.0f * textScale;
            float textW = label.length() * approxCharW;
            float textX = x + (w - textW) / 2.0f;
            float textY = y + (h - 20.0f * textScale) / 2.0f;
            
            // Draw shadow
            font->draw(renderer, textX + 2, textY + 2, label, textScale, {0, 0, 0, 180});
            // Draw main text
            font->draw(renderer, textX, textY, label, textScale, {255, 255, 255, 255});
        }
    };

    // Loading State Handlers (matching original main.cpp implementation)
    // Extracted to a helper to avoid complex inline lambda parsing issues on MSVC
    auto loadingRenderForwarder = [this](RenderManager& renderer) {
        // forward to helper defined below
        renderLoading(this, renderer);
    };
    m_stateManager->registerRenderHandler(AppState::Loading, loadingRenderForwarder);

    m_stateManager->registerUpdateHandler(AppState::Loading,
        [this](float deltaTime) {
            // Update 3D starfield so stars move during loading
            if (m_starfield3D) {
                // deltaTime here is in milliseconds; Starfield3D expects seconds
                m_starfield3D->update(deltaTime / 1000.0f);
            }

            // Check if loading is complete and transition to next stage
            if (m_assetManager->isLoadingComplete()) {
                SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Loading complete, handling post-load flow");

                // Update texture pointers now that assets are loaded
                m_stateContext.backgroundTex = m_assetManager->getTexture("background");
                m_stateContext.blocksTex = m_assetManager->getTexture("blocks");

                            // If an intro video exists and hasn't been started, attempt to play it in-process
                            std::filesystem::path introPath = m_introPath;
                            if (!m_introStarted && std::filesystem::exists(introPath)) {
                                SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Intro video found: %s", introPath.string().c_str());
                                try {
                                    if (!m_videoPlayer) m_videoPlayer = std::make_unique<VideoPlayer>();
                                    SDL_Renderer* sdlRend = (m_renderManager) ? m_renderManager->getSDLRenderer() : nullptr;
                                    if (m_videoPlayer->open(introPath.string(), sdlRend)) {
                                        m_introStarted = true;
                                        SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Intro video started in-process");
                                    } else {
                                        SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "VideoPlayer failed to open intro; skipping");
                                        m_stateManager->setState(AppState::Playing);
                                    }
                                } catch (const std::exception& ex) {
                                    SDL_LogWarn(SDL_LOG_CATEGORY_APPLICATION, "Exception while starting VideoPlayer: %s", ex.what());
                                    m_stateManager->setState(AppState::Playing);
                                }
                            } else if (m_introStarted) {
                                // Let VideoPlayer decode frames; once finished, transition to playing
                                if (m_videoPlayer) m_videoPlayer->update();
                                if (!m_videoPlayer || m_videoPlayer->isFinished()) {
                                    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Intro video finished (in-process), transitioning to Playing");
                                    m_stateManager->setState(AppState::Playing);
                                }
                            } else {
                                // No intro to play; transition directly to Playing
                                SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "No intro video; transitioning to Playing");
                                m_stateManager->setState(AppState::Playing);
                            }
            }
        });

    // Menu State render: draw background full-screen, then delegate to MenuState::render
    m_stateManager->registerRenderHandler(AppState::Menu,
        [this](RenderManager& renderer) {
            // Clear and draw background to full window
            renderer.clear(0, 0, 20, 255);
            int winW = 0, winH = 0;
            if (m_renderManager) m_renderManager->getWindowSize(winW, winH);
            SDL_Texture* background = m_assetManager->getTexture("background");
            if (background && winW > 0 && winH > 0) {
                SDL_FRect bgRect = { 0, 0, (float)winW, (float)winH };
                renderer.renderTexture(background, nullptr, &bgRect);
            }

            // Use RenderManager's computed logical viewport/scale for exact centering
            SDL_Rect logicalVP = {0,0,0,0};
            float logicalScale = 1.0f;
            if (m_renderManager) {
                logicalVP = m_renderManager->getLogicalViewport();
                logicalScale = m_renderManager->getLogicalScale();
            }
            SDL_SetRenderViewport(renderer.getSDLRenderer(), &logicalVP);
            SDL_SetRenderScale(renderer.getSDLRenderer(), logicalScale, logicalScale);
            if (m_menuState) {
                m_menuState->render(renderer.getSDLRenderer(), logicalScale, logicalVP);
            }
            // Reset to defaults
            SDL_SetRenderViewport(renderer.getSDLRenderer(), nullptr);
            SDL_SetRenderScale(renderer.getSDLRenderer(), 1.0f, 1.0f);
        });

    m_stateManager->registerRenderHandler(AppState::Options,
        [this](RenderManager& renderer) {
            renderer.clear(0, 0, 20, 255);
            int winW = 0, winH = 0;
            if (m_renderManager) m_renderManager->getWindowSize(winW, winH);
            SDL_Texture* background = m_assetManager->getTexture("background");
            if (background && winW > 0 && winH > 0) {
                SDL_FRect bgRect = { 0, 0, (float)winW, (float)winH };
                renderer.renderTexture(background, nullptr, &bgRect);
            }

            SDL_Rect logicalVP = {0,0,0,0};
            float logicalScale = 1.0f;
            if (m_renderManager) {
                logicalVP = m_renderManager->getLogicalViewport();
                logicalScale = m_renderManager->getLogicalScale();
            }
            SDL_SetRenderViewport(renderer.getSDLRenderer(), &logicalVP);
            SDL_SetRenderScale(renderer.getSDLRenderer(), logicalScale, logicalScale);
            if (m_optionsState) {
                m_optionsState->render(renderer.getSDLRenderer(), logicalScale, logicalVP);
            }
            SDL_SetRenderViewport(renderer.getSDLRenderer(), nullptr);
            SDL_SetRenderScale(renderer.getSDLRenderer(), 1.0f, 1.0f);
        });

    // LevelSelector State render: draw background full-screen, then delegate to LevelSelectorState::render
    m_stateManager->registerRenderHandler(AppState::LevelSelector,
        [this](RenderManager& renderer) {
            // Clear and draw background to full window
            renderer.clear(0, 0, 20, 255);
            int winW = 0, winH = 0;
            if (m_renderManager) m_renderManager->getWindowSize(winW, winH);
            SDL_Texture* background = m_assetManager->getTexture("background");
            if (background && winW > 0 && winH > 0) {
                SDL_FRect bgRect = { 0, 0, (float)winW, (float)winH };
                renderer.renderTexture(background, nullptr, &bgRect);
            }

            // Use RenderManager's computed logical viewport/scale for exact centering
            SDL_Rect logicalVP = {0,0,0,0};
            float logicalScale = 1.0f;
            if (m_renderManager) {
                logicalVP = m_renderManager->getLogicalViewport();
                logicalScale = m_renderManager->getLogicalScale();
            }
            SDL_SetRenderViewport(renderer.getSDLRenderer(), &logicalVP);
            SDL_SetRenderScale(renderer.getSDLRenderer(), logicalScale, logicalScale);
            if (m_levelSelectorState) {
                m_levelSelectorState->render(renderer.getSDLRenderer(), logicalScale, logicalVP);
            }
            // Reset to defaults
            SDL_SetRenderViewport(renderer.getSDLRenderer(), nullptr);
            SDL_SetRenderScale(renderer.getSDLRenderer(), 1.0f, 1.0f);
        });

    m_stateManager->registerUpdateHandler(AppState::Menu,
        [this](float deltaTime) {
            // Update logo animation counter
            // deltaTime is in milliseconds; keep same behavior as main.cpp: counter += frameMs * 0.0008
            m_logoAnimCounter += (deltaTime * static_cast<float>(Config::Animation::LOGO_ANIM_SPEED));
            // Also keep GlobalState's counter in sync for UI effects that read from it
            GlobalState::instance().logoAnimCounter += (deltaTime * Config::Animation::LOGO_ANIM_SPEED);
            
            // Update fireworks effect
            GlobalState& globalState = GlobalState::instance();
            // updateFireworks expects milliseconds
            globalState.updateFireworks(deltaTime);

            // Start music as soon as at least one track has decoded (don’t wait for all)
                // Start music as soon as at least one track has decoded (don't wait for all)
            if (m_musicEnabled && !m_musicStarted) {
                if (auto sys = AudioManager::get()) {
                    if (sys->getLoadedTrackCount() > 0) { sys->shuffle(); sys->start(); m_musicStarted = true; }
                }
            }
            // Track completion status for UI
            if (!m_musicLoaded) {
                if (auto sys = AudioManager::get()) {
                    if (sys->isLoadingComplete()) m_musicLoaded = true;
                }
            }
        });

    m_stateManager->registerEventHandler(AppState::Menu,
        [this](const SDL_Event& event) {
            // Forward keyboard events (Enter/Escape) to trigger actions, match original main.cpp
            if (event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat) {
                if (event.key.scancode == SDL_SCANCODE_RETURN || event.key.scancode == SDL_SCANCODE_RETURN2 || event.key.scancode == SDL_SCANCODE_KP_ENTER) {
                    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Starting game from menu (Enter)");
                    // Reset start level and transition
                    // In the original main, game.reset(...) was called; here we only switch state.
                    m_stateManager->setState(AppState::Playing);
                    return;
                }
                if (event.key.scancode == SDL_SCANCODE_ESCAPE) {
                    // If an exit confirmation is already showing, accept it and quit.
                    if (m_showExitConfirmPopup) {
                        SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Quitting from menu (Escape confirmed)");
                        m_running = false;
                        return;
                    }

                    // Otherwise, show the exit confirmation popup instead of quitting immediately.
                    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Showing exit confirmation (Escape)");
                    m_showExitConfirmPopup = true;
                    return;
                }
                // K: toggle SFX enable state (music handled globally)
                if (event.key.scancode == SDL_SCANCODE_K) {
                    SoundEffectManager::instance().setEnabled(!SoundEffectManager::instance().isEnabled());
                }
            }

            // Mouse handling: map SDL mouse coords into logical content coords and
            // perform hit-tests for menu buttons similar to main.cpp.
            if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN) {
                float mx = (float)event.button.x;
                float my = (float)event.button.y;
                // Use RenderManager's computed logical viewport/scale for precise mapping
                SDL_Rect logicalVP{0,0,0,0}; float logicalScale = 1.0f;
                if (m_renderManager) { logicalVP = m_renderManager->getLogicalViewport(); logicalScale = m_renderManager->getLogicalScale(); }
                // Check bounds and compute content-local coords
                if (mx >= logicalVP.x && my >= logicalVP.y && mx <= logicalVP.x + logicalVP.w && my <= logicalVP.y + logicalVP.h) {
                    float lx = (mx - logicalVP.x) / logicalScale;
                    float ly = (my - logicalVP.y) / logicalScale;

                    // Compute dynamic logical dimensions from viewport/scale
                    float dynW = (logicalScale > 0.f && logicalVP.w > 0) ? (float)logicalVP.w / logicalScale : (float)Config::Logical::WIDTH;
                    float dynH = (logicalScale > 0.f && logicalVP.h > 0) ? (float)logicalVP.h / logicalScale : (float)Config::Logical::HEIGHT;

                    // Respect settings popup
                    if (m_showSettingsPopup) {
                        m_showSettingsPopup = false;
                    } else {
                        bool isSmall = ((dynW * logicalScale) < 700.0f);
                        float btnW = isSmall ? (dynW * 0.4f) : 300.0f;
                        float btnH = isSmall ? 60.0f : 70.0f;
                        float btnCX = dynW * 0.5f;
                        const float btnYOffset = 40.0f;
                        float btnCY = dynH * 0.86f + btnYOffset;
                        SDL_FRect playBtn{btnCX - btnW * 0.6f - btnW/2.0f, btnCY - btnH/2.0f, btnW, btnH};
                        SDL_FRect levelBtn{btnCX + btnW * 0.6f - btnW/2.0f, btnCY - btnH/2.0f, btnW, btnH};

                        if (lx >= playBtn.x && lx <= playBtn.x + playBtn.w && ly >= playBtn.y && ly <= playBtn.y + playBtn.h) {
                            SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Menu: Play button clicked");
                            m_stateManager->setState(AppState::Playing);
                        } else if (lx >= levelBtn.x && lx <= levelBtn.x + levelBtn.w && ly >= levelBtn.y && ly <= levelBtn.y + levelBtn.h) {
                            SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Menu: Level button clicked");
                            m_stateManager->setState(AppState::LevelSelector);
                        } else {
                            // Settings area detection (top-right small area)
                            SDL_FRect settingsBtn{dynW - 60, 10, 50, 30};
                            if (lx >= settingsBtn.x && lx <= settingsBtn.x + settingsBtn.w && ly >= settingsBtn.y && ly <= settingsBtn.y + settingsBtn.h) {
                                m_showSettingsPopup = true;
                            }
                        }
                    }
                }
            }

            // Mouse motion handling for hover
            if (event.type == SDL_EVENT_MOUSE_MOTION) {
                float mx = (float)event.motion.x;
                float my = (float)event.motion.y;
                // Use RenderManager's computed logical viewport/scale for precise mapping
                SDL_Rect logicalVP{0,0,0,0}; float logicalScale = 1.0f;
                if (m_renderManager) { logicalVP = m_renderManager->getLogicalViewport(); logicalScale = m_renderManager->getLogicalScale(); }
                if (mx >= logicalVP.x && my >= logicalVP.y && mx <= logicalVP.x + logicalVP.w && my <= logicalVP.y + logicalVP.h) {
                    float lx = (mx - logicalVP.x) / logicalScale;
                    float ly = (my - logicalVP.y) / logicalScale;
                    if (!m_showSettingsPopup) {
                        // Compute dynamic logical dimensions
                        float dynW = (logicalScale > 0.f && logicalVP.w > 0) ? (float)logicalVP.w / logicalScale : (float)Config::Logical::WIDTH;
                        float dynH = (logicalScale > 0.f && logicalVP.h > 0) ? (float)logicalVP.h / logicalScale : (float)Config::Logical::HEIGHT;
                        bool isSmall = ((dynW * logicalScale) < 700.0f);
                        float btnW = isSmall ? (dynW * 0.4f) : 300.0f;
                        float btnH = isSmall ? 60.0f : 70.0f;
                        float btnCX = dynW * 0.5f;
                        const float btnYOffset = 40.0f;
                        float btnCY = dynH * 0.86f + btnYOffset;
                        SDL_FRect playBtn{btnCX - btnW * 0.6f - btnW/2.0f, btnCY - btnH/2.0f, btnW, btnH};
                        SDL_FRect levelBtn{btnCX + btnW * 0.6f - btnW/2.0f, btnCY - btnH/2.0f, btnW, btnH};
                        m_hoveredButton = -1;
                        if (lx >= playBtn.x && lx <= playBtn.x + playBtn.w && ly >= playBtn.y && ly <= playBtn.y + playBtn.h) {
                            m_hoveredButton = 0;
                        } else if (lx >= levelBtn.x && lx <= levelBtn.x + levelBtn.w && ly >= levelBtn.y && ly <= levelBtn.y + levelBtn.h) {
                            m_hoveredButton = 1;
                        }
                    }
                }
            }
        });

    // GameOver State - Handle restart and return to menu
    m_stateManager->registerEventHandler(AppState::GameOver,
        [this](const SDL_Event& event) {
            if (event.type == SDL_EVENT_KEY_DOWN && !event.key.repeat) {
                // Enter/Space - restart game
                if (event.key.scancode == SDL_SCANCODE_RETURN || 
                    event.key.scancode == SDL_SCANCODE_RETURN2 || 
                    event.key.scancode == SDL_SCANCODE_KP_ENTER ||
                    event.key.scancode == SDL_SCANCODE_SPACE) {
                    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Restarting game from GameOver (Enter/Space)");
                    // Reset game with current start level and transition to Playing
                    if (m_stateContext.game) {
                        m_stateContext.game->reset(m_startLevelSelection);
                    }
                    m_stateManager->setState(AppState::Playing);
                    return;
                }
                // Escape - return to menu
                if (event.key.scancode == SDL_SCANCODE_ESCAPE) {
                    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Returning to menu from GameOver (Escape)");
                    m_stateManager->setState(AppState::Menu);
                    return;
                }
            }
        });

    // Playing State - Full game rendering
    m_stateManager->registerEventHandler(AppState::Playing,
        [this](const SDL_Event& event) {
            // Handle mouse clicks on the exit confirmation popup
            if (event.type == SDL_EVENT_MOUSE_BUTTON_DOWN && m_showExitConfirmPopup) {
                float mx = (float)event.button.x;
                float my = (float)event.button.y;

                int winW = 0, winH = 0;
                if (m_renderManager) m_renderManager->getWindowSize(winW, winH);

                const float LOGICAL_W = static_cast<float>(Config::Logical::WIDTH);
                const float LOGICAL_H = static_cast<float>(Config::Logical::HEIGHT);
                float scaleX = (winW > 0) ? (float)winW / LOGICAL_W : 1.0f;
                float scaleY = (winH > 0) ? (float)winH / LOGICAL_H : 1.0f;
                float logicalScale = std::min(scaleX, scaleY);

                SDL_Rect logicalVP{0, 0, winW, winH};
                if (mx < logicalVP.x || my < logicalVP.y || mx > logicalVP.x + logicalVP.w || my > logicalVP.y + logicalVP.h) return;

                float lx = (mx - logicalVP.x) / (logicalScale > 0.f ? logicalScale : 1.f);
                float ly = (my - logicalVP.y) / (logicalScale > 0.f ? logicalScale : 1.f);

                // Compute content offsets to convert to content-local logical coords (what renderer uses)
                float contentW = LOGICAL_W * logicalScale;
                float contentH = LOGICAL_H * logicalScale;
                float contentOffsetX = (winW - contentW) * 0.5f / (logicalScale > 0.f ? logicalScale : 1.f);
                float contentOffsetY = (winH - contentH) * 0.5f / (logicalScale > 0.f ? logicalScale : 1.f);
                float localX = lx - contentOffsetX;
                float localY = ly - contentOffsetY;

                // Popup geometry (must match GameRenderer)
                float popupW = 420.0f, popupH = 180.0f;
                float popupX = (LOGICAL_W - popupW) * 0.5f;
                float popupY = (LOGICAL_H - popupH) * 0.5f;
                float btnW = 140.0f, btnH = 46.0f;
                float yesX = popupX + popupW * 0.25f - btnW * 0.5f;
                float noX  = popupX + popupW * 0.75f - btnW * 0.5f;
                float btnY = popupY + popupH - 60.0f;

                // Only react if click is inside popup
                if (localX >= popupX && localX <= popupX + popupW && localY >= popupY && localY <= popupY + popupH) {
                    if (localX >= yesX && localX <= yesX + btnW && localY >= btnY && localY <= btnY + btnH) {
                        // YES: go back to menu (reset game)
                        m_showExitConfirmPopup = false;
                        if (m_stateContext.game) m_stateContext.game->reset(m_startLevelSelection);
                        if (m_stateManager) m_stateManager->setState(AppState::Menu);
                        return;
                    }
                    if (localX >= noX && localX <= noX + btnW && localY >= btnY && localY <= btnY + btnH) {
                        // NO: close popup and resume
                        m_showExitConfirmPopup = false;
                        if (m_stateContext.game) m_stateContext.game->setPaused(false);
                        return;
                    }
                }
            }
        });
    m_stateManager->registerRenderHandler(AppState::Playing,
        [this](RenderManager& renderer) {
            // Clear the screen first
            renderer.clear(0, 0, 0, 255);

            // Window size
            int winW = 0, winH = 0;
            renderer.getWindowSize(winW, winH);

            // Draw per-level background stretched to full window, with fade
            if (m_stateContext.game) {
                // Update fade progression (ms based on frame time not available here; approximate using SDL ticks delta if desired)
                // We'll keep alpha as-is; Loading/Menu update can adjust if we wire a timer. For now, simply show the correct background.
                int currentLevel = m_stateContext.game->level();
                int bgLevel = (currentLevel > 32) ? 32 : currentLevel; // Cap at 32 like main.cpp

                if (m_cachedBgLevel != bgLevel) {
                    if (m_nextLevelBackgroundTex) { SDL_DestroyTexture(m_nextLevelBackgroundTex); m_nextLevelBackgroundTex = nullptr; }
                    char bgPath[256];
                    std::snprintf(bgPath, sizeof(bgPath), "assets/images/tetris_main_back_level%d.jpg", bgLevel);
                    const std::string resolvedBgPath = AssetPath::resolveImagePath(bgPath);
                    SDL_Surface* s = IMG_Load(resolvedBgPath.c_str());
                    if (s && renderer.getSDLRenderer()) {
                        m_nextLevelBackgroundTex = SDL_CreateTextureFromSurface(renderer.getSDLRenderer(), s);
                        SDL_DestroySurface(s);
                        m_levelFadeAlpha = 0.0f;
                        m_levelFadeElapsed = 0.0f;
                        m_cachedBgLevel = bgLevel;
                        if (resolvedBgPath != bgPath) {
                            SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Loaded level %d background via %s", bgLevel, resolvedBgPath.c_str());
                        }
                    } else {
                        m_cachedBgLevel = -1; // don't change if missing
                        if (s) SDL_DestroySurface(s);
                    }
                }

                if (winW > 0 && winH > 0) {
                    SDL_FRect full{0,0,(float)winW,(float)winH};
                    if (m_nextLevelBackgroundTex && m_levelFadeAlpha < 1.0f && m_levelBackgroundTex) {
                        SDL_SetTextureAlphaMod(m_levelBackgroundTex, Uint8((1.0f - m_levelFadeAlpha) * 255));
                        SDL_RenderTexture(renderer.getSDLRenderer(), m_levelBackgroundTex, nullptr, &full);
                        SDL_SetTextureAlphaMod(m_nextLevelBackgroundTex, Uint8(m_levelFadeAlpha * 255));
                        SDL_RenderTexture(renderer.getSDLRenderer(), m_nextLevelBackgroundTex, nullptr, &full);
                        SDL_SetTextureAlphaMod(m_levelBackgroundTex, 255);
                        SDL_SetTextureAlphaMod(m_nextLevelBackgroundTex, 255);
                    } else if (m_nextLevelBackgroundTex && (!m_levelBackgroundTex || m_levelFadeAlpha >= 1.0f)) {
                        if (m_levelBackgroundTex) SDL_DestroyTexture(m_levelBackgroundTex);
                        m_levelBackgroundTex = m_nextLevelBackgroundTex;
                        m_nextLevelBackgroundTex = nullptr;
                        m_levelFadeAlpha = 0.0f;
                        SDL_RenderTexture(renderer.getSDLRenderer(), m_levelBackgroundTex, nullptr, &full);
                    } else if (m_levelBackgroundTex) {
                        SDL_RenderTexture(renderer.getSDLRenderer(), m_levelBackgroundTex, nullptr, &full);
                    }
                }
            }

            // Compute logical scale from logical design size
            const float LOGICAL_W = static_cast<float>(Config::Logical::WIDTH);
            const float LOGICAL_H = static_cast<float>(Config::Logical::HEIGHT);
            float scaleX = (winW > 0) ? (float)winW / LOGICAL_W : 1.0f;
            float scaleY = (winH > 0) ? (float)winH / LOGICAL_H : 1.0f;
            float logicalScale = std::min(scaleX, scaleY);

            // Use full-window viewport; GameRenderer applies its own content offsets for centering
            SDL_Rect logicalVP = {0, 0, winW, winH};
            SDL_SetRenderViewport(renderer.getSDLRenderer(), &logicalVP);

            // Use GameRenderer for actual game rendering
            GameRenderer::renderPlayingState(
                renderer.getSDLRenderer(),
                m_stateContext.game,
                m_stateContext.pixelFont,
                m_stateContext.lineEffect,
                m_stateContext.blocksTex,
                m_stateContext.asteroidsTex,
                m_stateContext.statisticsPanelTex,
                m_stateContext.scorePanelTex,
                m_stateContext.nextPanelTex,
                m_stateContext.holdPanelTex,
                false,
                LOGICAL_W,
                LOGICAL_H,
                logicalScale,
                static_cast<float>(winW),
                static_cast<float>(winH)
            );

            // Reset viewport
            SDL_SetRenderViewport(renderer.getSDLRenderer(), nullptr);
        });

    // GameOver State - Simple game over screen
    m_stateManager->registerRenderHandler(AppState::GameOver,
        [this](RenderManager& renderer) {
            // Clear the screen first
            renderer.clear(12, 12, 16, 255);
            
            // Calculate viewport and scale for responsive layout
            int winW = 0, winH = 0;
            renderer.getWindowSize(winW, winH);
            
            const float LOGICAL_W = static_cast<float>(Config::Window::DEFAULT_WIDTH);
            const float LOGICAL_H = static_cast<float>(Config::Window::DEFAULT_HEIGHT);
            
            float scaleX = static_cast<float>(winW) / LOGICAL_W;
            float scaleY = static_cast<float>(winH) / LOGICAL_H;
            float logicalScale = std::min(scaleX, scaleY);
            
            int scaledW = static_cast<int>(LOGICAL_W * logicalScale);
            int scaledH = static_cast<int>(LOGICAL_H * logicalScale);
            int offsetX = (winW - scaledW) / 2;
            int offsetY = (winH - scaledH) / 2;
            
            SDL_Rect logicalVP = {offsetX, offsetY, scaledW, scaledH};
            SDL_SetRenderViewport(renderer.getSDLRenderer(), &logicalVP);
            
            // Draw starfield background
            if (m_starfield) {
                m_starfield->draw(renderer.getSDLRenderer());
            }
            
            // Game over text and stats
            if (m_stateContext.pixelFont && m_stateContext.game) {
                FontAtlas& font = *m_stateContext.pixelFont;
                
                // "GAME OVER" title
                font.draw(renderer.getSDLRenderer(), LOGICAL_W * 0.5f - 120, 140, "GAME OVER", 3.0f, {255, 80, 60, 255});
                
                // Game stats (single-player or coop combined)
                char buf[128];
                if (m_stateContext.game && m_stateContext.game->getMode() == GameMode::Cooperate && m_stateContext.coopGame) {
                    int leftScore = m_stateContext.coopGame->score(::CoopGame::PlayerSide::Left);
                    int rightScore = m_stateContext.coopGame->score(::CoopGame::PlayerSide::Right);
                    int total = leftScore + rightScore;
                    std::snprintf(buf, sizeof(buf), "SCORE %d + %d = %d  LINES %d  LEVEL %d",
                        leftScore,
                        rightScore,
                        total,
                        m_stateContext.coopGame->lines(),
                        m_stateContext.coopGame->level());
                } else {
                    std::snprintf(buf, sizeof(buf), "SCORE %d  LINES %d  LEVEL %d",
                        m_stateContext.game ? m_stateContext.game->score() : 0,
                        m_stateContext.game ? m_stateContext.game->lines() : 0,
                        m_stateContext.game ? m_stateContext.game->level() : 0);
                }
                font.draw(renderer.getSDLRenderer(), LOGICAL_W * 0.5f - 220, 220, buf, 1.2f, {220, 220, 230, 255});
                
                // Instructions
                font.draw(renderer.getSDLRenderer(), LOGICAL_W * 0.5f - 120, 270, "PRESS ENTER / SPACE", 1.2f, {200, 200, 220, 255});
            }
            
            // Reset viewport
            SDL_SetRenderViewport(renderer.getSDLRenderer(), nullptr);
        });

    // Playing State - Update handler for DAS/ARR movement timing
    m_stateManager->registerUpdateHandler(AppState::Playing,
        [this](double frameMs) {
            if (!m_stateContext.game) return;

            const bool coopActive = m_stateContext.game->getMode() == GameMode::Cooperate && m_stateContext.coopGame;

            // Get current keyboard state
            const bool *ks = SDL_GetKeyboardState(nullptr);

            if (coopActive) {
                // Paused: suppress all continuous input so pieces don't drift while paused.
                if (m_stateContext.game->isPaused()) {
                    m_stateContext.coopGame->setSoftDropping(CoopGame::PlayerSide::Left, false);
                    m_stateContext.coopGame->setSoftDropping(CoopGame::PlayerSide::Right, false);
                    m_p1MoveTimerMs = 0.0;
                    m_p2MoveTimerMs = 0.0;
                    m_p1LeftHeld = false;
                    m_p1RightHeld = false;
                    m_p2LeftHeld = false;
                    m_p2RightHeld = false;
                    return;
                }

                auto handleSide = [&](CoopGame::PlayerSide side,
                                      bool leftHeld,
                                      bool rightHeld,
                                      double& timer,
                                      SDL_Scancode leftKey,
                                      SDL_Scancode rightKey,
                                      SDL_Scancode downKey) {
                    bool left = ks[leftKey];
                    bool right = ks[rightKey];
                    bool down = ks[downKey];

                    // Soft drop flag
                    m_stateContext.coopGame->setSoftDropping(side, down);

                    int moveDir = 0;
                    if (left && !right) moveDir = -1;
                    else if (right && !left) moveDir = +1;

                    if (moveDir != 0) {
                        if ((moveDir == -1 && !leftHeld) || (moveDir == +1 && !rightHeld)) {
                            // First press - immediate movement
                            m_stateContext.coopGame->move(side, moveDir);
                            timer = DAS;
                        } else {
                            timer -= frameMs;
                            if (timer <= 0) {
                                m_stateContext.coopGame->move(side, moveDir);
                                timer += ARR;
                            }
                        }
                    } else {
                        timer = 0.0;
                    }

                    // Soft drop boost: coop uses same gravity path; fall acceleration handled inside tickGravity
                };

                // Left player (WASD): A/D horizontal, S soft drop
                handleSide(CoopGame::PlayerSide::Left, m_p1LeftHeld, m_p1RightHeld, m_p1MoveTimerMs,
                           SDL_SCANCODE_A, SDL_SCANCODE_D, SDL_SCANCODE_S);
                // Right player (arrows): Left/Right horizontal, Down soft drop
                handleSide(CoopGame::PlayerSide::Right, m_p2LeftHeld, m_p2RightHeld, m_p2MoveTimerMs,
                           SDL_SCANCODE_LEFT, SDL_SCANCODE_RIGHT, SDL_SCANCODE_DOWN);

                // Update held flags for next frame
                m_p1LeftHeld = ks[SDL_SCANCODE_A];
                m_p1RightHeld = ks[SDL_SCANCODE_D];
                m_p2LeftHeld = ks[SDL_SCANCODE_LEFT];
                m_p2RightHeld = ks[SDL_SCANCODE_RIGHT];

                // Gravity / effects
                m_stateContext.coopGame->tickGravity(frameMs);
                m_stateContext.coopGame->updateVisualEffects(frameMs);

                // Delegate to PlayingState for any ancillary updates (renderer transport bookkeeping)
                if (m_playingState) {
                    m_playingState->update(frameMs);
                }

                // Game over transition for coop
                if (m_stateContext.coopGame->isGameOver()) {
                    m_stateManager->setState(AppState::GameOver);
                }

            } else {
                bool left = ks[SDL_SCANCODE_LEFT] || ks[SDL_SCANCODE_A];
                bool right = ks[SDL_SCANCODE_RIGHT] || ks[SDL_SCANCODE_D];
                bool down = ks[SDL_SCANCODE_DOWN] || ks[SDL_SCANCODE_S];
                
                // Handle soft drop
                m_stateContext.game->setSoftDropping(down && !m_stateContext.game->isPaused());
                
                // Handle DAS/ARR movement timing (from original main.cpp)
                int moveDir = 0;
                if (left && !right)
                    moveDir = -1;
                else if (right && !left)
                    moveDir = +1;
                
                if (moveDir != 0 && !m_stateContext.game->isPaused()) {
                    if ((moveDir == -1 && !m_leftHeld) || (moveDir == +1 && !m_rightHeld)) {
                        // First press - immediate movement
                        m_stateContext.game->move(moveDir);
                        m_moveTimerMs = DAS;  // Set initial delay
                    } else {
                        // Key held - handle repeat timing
                        m_moveTimerMs -= frameMs;
                        if (m_moveTimerMs <= 0) {
                            m_stateContext.game->move(moveDir);
                            m_moveTimerMs += ARR;  // Set repeat rate
                        }
                    }
                } else {
                    m_moveTimerMs = 0;  // Reset timer when no movement
                }
                
                // Update held state for next frame
                m_leftHeld = left;
                m_rightHeld = right;
                
                // Handle soft drop boost
                if (down && !m_stateContext.game->isPaused()) {
                    m_stateContext.game->softDropBoost(frameMs);
                }
                
                // Delegate to PlayingState for other updates (gravity, line effects)
                if (m_playingState) {
                    m_playingState->update(frameMs);
                }

                // Update background fade progression (match main.cpp semantics approx)
                // Duration 1200ms fade (same as LEVEL_FADE_DURATION used in main.cpp snippets)
                const float LEVEL_FADE_DURATION = 1200.0f;
                if (m_nextLevelBackgroundTex) {
                    m_levelFadeElapsed += (float)frameMs;
                    m_levelFadeAlpha = std::min(1.0f, m_levelFadeElapsed / LEVEL_FADE_DURATION);
                }
                
                // Check for game over and transition to GameOver state
                if (m_stateContext.game->isGameOver()) {
                    // Submit score before transitioning
                    if (m_stateContext.scores) {
                        std::string gt = (m_stateContext.game->getMode() == GameMode::Challenge) ? "challenge" : "classic";
                        m_stateContext.scores->submit(
                            m_stateContext.game->score(), 
                            m_stateContext.game->lines(), 
                            m_stateContext.game->level(), 
                            m_stateContext.game->elapsed(),
                            std::string("PLAYER"),
                            gt
                        );
                    }
                    m_stateManager->setState(AppState::GameOver);
                }
            }
        });
    // Debug overlay: show current window and logical sizes on the right side of the screen
    auto debugOverlay = [this](RenderManager& renderer) {
        // Window size
        int winW = 0, winH = 0;
        renderer.getWindowSize(winW, winH);

        // Logical viewport and scale
        SDL_Rect logicalVP{0,0,0,0};
        float logicalScale = 1.0f;
        if (m_renderManager) {
            logicalVP = m_renderManager->getLogicalViewport();
            logicalScale = m_renderManager->getLogicalScale();
        }

        // Use dynamic logical dimensions from GlobalState
        float LOGICAL_W = static_cast<float>(GlobalState::instance().getLogicalWidth());
        float LOGICAL_H = static_cast<float>(GlobalState::instance().getLogicalHeight());

        // Use logical viewport so overlay is aligned with game content
        SDL_SetRenderViewport(renderer.getSDLRenderer(), &logicalVP);
        SDL_SetRenderScale(renderer.getSDLRenderer(), logicalScale, logicalScale);

        // Choose font (pixel first, fallback to main)
        FontAtlas* pixelFont = (FontAtlas*)(m_assetManager ? m_assetManager->getFont("pixel_font") : nullptr);
        FontAtlas* mainFont = (FontAtlas*)(m_assetManager ? m_assetManager->getFont("main_font") : nullptr);
        FontAtlas* font = pixelFont ? pixelFont : mainFont;

        // Inline small helper for drawing a filled rect in logical coords
        auto fillRect = [&](float x, float y, float w, float h, SDL_Color c) {
            SDL_SetRenderDrawColor(renderer.getSDLRenderer(), c.r, c.g, c.b, c.a);
            SDL_FRect r{ x, y, w, h };
            SDL_RenderFillRect(renderer.getSDLRenderer(), &r);
        };

        // Prepare text lines
        char buf[128];
        std::snprintf(buf, sizeof(buf), "Win: %d x %d", winW, winH);
        std::string sWin(buf);
        std::snprintf(buf, sizeof(buf), "Logical: %.0f x %.0f", LOGICAL_W, LOGICAL_H);
        std::string sLogical(buf);
        std::snprintf(buf, sizeof(buf), "Scale: %.2f", logicalScale);
        std::string sScale(buf);

        // Determine size of longest line
        int w1=0,h1=0, w2=0,h2=0, w3=0,h3=0;
        if (font) {
            font->measure(sWin, 1.0f, w1, h1);
            font->measure(sLogical, 1.0f, w2, h2);
            font->measure(sScale, 1.0f, w3, h3);
        }
        int maxW = std::max({w1,w2,w3});
        int totalH = (h1 + h2 + h3) + 8; // small padding

        // Position based on actual screen width (center horizontally)
        const float margin = 8.0f;
        //        float x = (LOGICAL_W - (float)maxW) * 0.5f; // Center horizontally
        //        float y = margin;
                // Desired position in window (pixel) coords
                int winW_px = 0, winH_px = 0;
                renderer.getWindowSize(winW_px, winH_px);
                float desiredWinX = (float(winW_px) - (float)maxW) * 0.5f; // center on full window width
                float desiredWinY = margin; // near top of the window

                // Convert window coords to logical coords under current viewport/scale
                float invScale = (logicalScale > 0.0f) ? (1.0f / logicalScale) : 1.0f;
                float x = (desiredWinX - float(logicalVP.x)) * invScale;
                float y = (desiredWinY - float(logicalVP.y)) * invScale;

        // Draw background box for readability
        fillRect(x - 6.0f, y - 6.0f, (float)maxW + 12.0f, (float)totalH + 8.0f, {0, 0, 0, 180});

        // Draw text lines
        SDL_Color textColor = {255, 204, 0, 255};
        if (font) {
            font->draw(renderer.getSDLRenderer(), x, y, sWin, 1.0f, textColor);
            font->draw(renderer.getSDLRenderer(), x, y + (float)h1, sLogical, 1.0f, textColor);
            font->draw(renderer.getSDLRenderer(), x, y + (float)(h1 + h2), sScale, 1.0f, textColor);
        }

        // Reset viewport/scale
        SDL_SetRenderViewport(renderer.getSDLRenderer(), nullptr);
        SDL_SetRenderScale(renderer.getSDLRenderer(), 1.0f, 1.0f);
    };

    // Register debug overlay for all primary states so it draws on top
    if (m_stateManager) {
        m_stateManager->registerRenderHandler(AppState::Loading, debugOverlay);
        m_stateManager->registerRenderHandler(AppState::Menu, debugOverlay);
        m_stateManager->registerRenderHandler(AppState::LevelSelector, debugOverlay);
        m_stateManager->registerRenderHandler(AppState::Playing, debugOverlay);
        m_stateManager->registerRenderHandler(AppState::GameOver, debugOverlay);
    }

    auto helpOverlayRender = [this](RenderManager& renderer) {
        if (!m_showHelpOverlay || !m_stateContext.pixelFont) {
            return;
        }

        SDL_Renderer* sdlRenderer = renderer.getSDLRenderer();
        if (!sdlRenderer) {
            return;
        }

        SDL_Rect logicalVP = renderer.getLogicalViewport();
        float logicalScale = renderer.getLogicalScale();
        SDL_SetRenderViewport(sdlRenderer, &logicalVP);
        SDL_SetRenderScale(sdlRenderer, logicalScale, logicalScale);
        HelpOverlay::Render(
            sdlRenderer,
            *m_stateContext.pixelFont,
            static_cast<float>(Config::Logical::WIDTH),
            static_cast<float>(Config::Logical::HEIGHT),
            0.0f,
            0.0f
        );
        SDL_SetRenderViewport(sdlRenderer, nullptr);
        SDL_SetRenderScale(sdlRenderer, 1.0f, 1.0f);
    };

    if (m_stateManager) {
        m_stateManager->registerRenderHandler(AppState::Loading, helpOverlayRender);
        m_stateManager->registerRenderHandler(AppState::Menu, helpOverlayRender);
        m_stateManager->registerRenderHandler(AppState::Options, helpOverlayRender);
        m_stateManager->registerRenderHandler(AppState::LevelSelector, helpOverlayRender);
        m_stateManager->registerRenderHandler(AppState::Playing, helpOverlayRender);
        m_stateManager->registerRenderHandler(AppState::GameOver, helpOverlayRender);
    }
}

void ApplicationManager::processEvents() {
    // Let InputManager process all SDL events
    if (m_inputManager) {
        m_inputManager->processEvents();
        
        // Check if InputManager detected a quit request
        if (m_inputManager->shouldQuit()) {
            SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "InputManager reports shouldQuit() == true — requesting shutdown");
            requestShutdown();
            return;
        }
    }

    // Handle any additional events not processed by InputManager
    // (In this case, we rely on InputManager for most event handling)
}

void ApplicationManager::update(float deltaTime) {
    // Update AssetManager for progressive loading
    if (m_assetManager) {
        m_assetManager->update(deltaTime);
    }
    
    // Update InputManager
    if (m_inputManager) {
        m_inputManager->update(deltaTime);
    }

    // Always update 3D starfield so background animates even during loading/menu
    if (m_starfield3D) {
        m_starfield3D->update(deltaTime);
    }

    // Update StateManager
    if (m_stateManager) {
    // NOTE: State update handlers expect milliseconds (frameMs). Convert seconds -> ms here.
    float frameMs = deltaTime * 1000.0f;
    m_stateManager->update(frameMs);
    SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "ApplicationManager::update - state update completed for state %s", m_stateManager->getStateName(m_stateManager->getState()));
    traceFile("update completed");
    }
}

void ApplicationManager::render() {
    if (!m_renderManager) {
        return;
    }
    SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "ApplicationManager::render - about to begin frame");
    // Trace render begin
    traceFile("render begin");
    m_renderManager->beginFrame();
    SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "ApplicationManager::render - beginFrame complete");
    
    // Delegate rendering to StateManager
    if (m_stateManager) {
        m_stateManager->render(*m_renderManager);
        SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "ApplicationManager::render - state render completed for state %s", m_stateManager->getStateName(m_stateManager->getState()));
    }
    
    m_renderManager->endFrame();
    SDL_LogDebug(SDL_LOG_CATEGORY_APPLICATION, "ApplicationManager::render - endFrame complete");
    traceFile("render endFrame complete");
}

void ApplicationManager::cleanupManagers() {
    // Cleanup managers in reverse order
    // Destroy gameplay background textures
    if (m_levelBackgroundTex) { SDL_DestroyTexture(m_levelBackgroundTex); m_levelBackgroundTex = nullptr; }
    if (m_nextLevelBackgroundTex) { SDL_DestroyTexture(m_nextLevelBackgroundTex); m_nextLevelBackgroundTex = nullptr; }
    // Shutdown subsystems that own GPU resources before renderer destruction
    if (m_lineEffect) { m_lineEffect->shutdown(); }
    // Fonts are managed by AssetManager; ensure it shuts down after we stop states
    m_stateManager.reset();
    m_assetManager.reset();
    m_inputManager.reset();
    m_renderManager.reset();
    
    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Managers cleaned up");
}

void ApplicationManager::cleanupSDL() {
    TTF_Quit();
    SDL_Quit();
    SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "SDL cleaned up");
}
