/*
 * Copyright (c) 2021 Arm Limited and Contributors. All rights reserved.
 *
 * SPDX-License-Identifier: Apache-2.0
 * 
 * ============================================================================
 * REACTIVE GAME TEMPLATE - Event-Driven Architecture for RP2350
 * ============================================================================
 * 
 * This template provides a clean, reactive architecture for building games
 * and interactive applications on Raspberry Pi Pico with displays.
 * 
 * KEY FEATURES:
 * - Event-driven: Display only updates when input is received
 * - Power efficient: Uses __wfi() to sleep between inputs
 * - E-ink optimized: Minimizes screen refreshes
 * - Interrupt-driven: Touch and button handling via interrupts
 * - Modular: Clear separation of input, game logic, and rendering
 * 
 * ARCHITECTURE:
 * 1. Interrupt handlers set flags (kept minimal)
 * 2. Main loop processes input events
 * 3. Game logic updates state based on events
 * 4. Screen refreshes only when changes occur
 * 
 * HOW TO CREATE YOUR OWN GAME:
 * ============================================================================
 * 1. Modify GameState structure with your game variables
 * 2. Implement game_init() to set initial values
 * 3. Implement game_update() to handle input and update state
 * 4. Implement game_draw() to render your game graphics
 * 5. Adjust GameConfig for your game's needs
 * 6. The reactive loop and input system work automatically!
 * ============================================================================
 */

#include "pico/stdlib.h"
#include "pico/binary_info.h"
#include "hardware/sync.h"
#include "pico/multicore.h"
#include "board_config.h"  // Board-specific pin configuration
#include "sd_card.h"
extern "C" {
    #include "ff.h"  // FatFS
}
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "display/low_level_render.h"
#include "display/low_level_display.h"
#include "display/low_level_display_epaper.h"
#include "display/low_level_display_st7796.h"
#include "display/low_level_touch.h"
#include "input_manager.h"
#include "game.h"
#include "game_launcher.h"
#include "tic_tac_toe.h"
#include "demo_game.h"
#include "monopoly_game.h"
#include "lua_game_loader.h"


// Binary info for RP2350 - ensures proper boot image structure
bi_decl(bi_program_description("4.0\" TFT ST7796 with Touch and SD Card Demo"));
bi_decl(bi_program_version_string("0.1"));
bi_decl(bi_program_build_date_string(__DATE__));

// ============================================================================
// DUAL-CORE DISPLAY REFRESH SYSTEM
// ============================================================================

// Shared variables for core communication
volatile bool refresh_requested = false;
volatile bool refresh_in_progress = false;
const uint8_t* volatile refresh_buffer = nullptr;
LowLevelDisplay* volatile refresh_display = nullptr;

/**
 * @brief Core 1 entry point - handles display refresh operations
 * 
 * Runs on the second core, waiting for refresh requests.
 * This keeps Core 0 responsive while display updates happen in background.
 */
void core1_entry() {
    printf("Core 1 started - handling display refreshes\n");
    
    while (1) {
        // Wait for refresh request
        if (refresh_requested && refresh_buffer && refresh_display) {
            refresh_in_progress = true;
            
            // Get local copies for safe access
            LowLevelDisplay* display = refresh_display;
            const uint8_t* buffer = refresh_buffer;
            
            // Perform the refresh operation (may be slow for e-ink)
            display->draw_buffer(buffer);
            display->refresh();
            
            // Clear flags
            refresh_requested = false;
            refresh_in_progress = false;
        }
        
        // Small delay to avoid busy-waiting
        sleep_us(100);
    }
}

/**
 * @brief Request a screen refresh (non-blocking)
 * 
 * Queues the refresh on Core 1, keeping Core 0 responsive.
 * 
 * @param buffer Pointer to 1-bit framebuffer
 * @param display Pointer to display abstraction
 * @return true if refresh started, false if already in progress
 */
bool refresh_screen_async(const uint8_t *buffer, LowLevelDisplay* display) {
    // Check if Core 1 is busy with previous refresh
    if (refresh_in_progress) {
        // Still refreshing previous frame, skip this one
        return false;
    }
    
    // Queue refresh on Core 1
    refresh_buffer = buffer;
    refresh_display = display;
    refresh_requested = true;
    
    return true;
}

/**
 * @brief Check if a refresh is currently in progress
 * @return true if Core 1 is still refreshing
 */
bool is_refresh_in_progress() {
    return refresh_in_progress;
}

// ============================================================================
// GAME CONFIGURATION
// ============================================================================

// Game configuration - adjust these for your game
struct GameConfig {
    uint32_t touch_debounce_ms;    // Touch polling rate
    uint32_t button_debounce_ms;   // Button debounce delay
    bool enable_gestures;          // Enable gesture recognition
    bool enable_continuous_draw;   // Allow continuous drawing while touched
    bool debug_verbose;            // Print debug messages
};

// ============================================================================
// DISPLAY DIMMING CONFIGURATION
// ============================================================================

// Display dimming settings
#define DIM_TIMEOUT_MS (2 * 60 * 1000)   // 2 minutes to dim
#define SLEEP_TIMEOUT_MS (10 * 60 * 1000) // 10 minutes to sleep
#define DIM_CHECK_INTERVAL_MS 10000      // Check every 10 seconds

// Display dimming state
static uint32_t last_interaction_time = 0;  // Last time user interacted
static bool is_idle_2min_triggered = false; // Flag for 2min trigger
static bool is_idle_10min_triggered = false; // Flag for 10min trigger
static volatile bool dim_check_flag = false;  // Flag set by timer to check dimming
static LowLevelDisplay* global_display = nullptr;  // Global display pointer for timer callback

/**
 * @brief Update last interaction time and notify display driver
 * 
 * Call this whenever the user interacts with the device (touch, button press).
 * The display driver handles specific wake/restore logic.
 * 
 * @param display Pointer to display interface
 */
static inline void record_user_interaction(LowLevelDisplay* display) {
    last_interaction_time = to_ms_since_boot(get_absolute_time());
    
    // Reset idle flags
    is_idle_2min_triggered = false;
    is_idle_10min_triggered = false;
    
    // Notify display driver of interaction
    display->on_user_interaction();
}

/**
 * @brief Timer callback to periodically check dimming status
 * 
 * This alarm callback fires every DIM_CHECK_INTERVAL_MS milliseconds
 * to wake the CPU from __wfi() and check if dimming should occur.
 * Running in interrupt context, so just sets a flag for main loop.
 * 
 * @param id Alarm ID (unused)
 * @param user_data User data pointer (unused)
 * @return Next alarm time (relative to current time)
 */
static int64_t dim_check_alarm_callback(alarm_id_t id, void *user_data) {
    // Set flag to check dimming in main loop
    dim_check_flag = true;
    
    // Return interval in microseconds for next alarm
    // Negative value means schedule relative to now
    return -(DIM_CHECK_INTERVAL_MS * 1000);
}

/**
 * @brief Check if idle thresholds have been met and notify display driver
 * 
 * Checks elapsed time since last interaction and calls the appropriate
 * display driver methods (on_idle_2min or on_idle_10min).
 * 
 * @param display Pointer to display interface
 */
static inline void check_and_apply_dimming(LowLevelDisplay* display) {
    uint32_t current_time = to_ms_since_boot(get_absolute_time());
    uint32_t elapsed = current_time - last_interaction_time;
    
    // Check for 10 minute timeout (Sleep)
    if (!is_idle_10min_triggered && elapsed >= SLEEP_TIMEOUT_MS) {
        display->on_idle_10min();
        is_idle_10min_triggered = true;
        is_idle_2min_triggered = true; // Implicitly triggered
    }
    // Check for 2 minute timeout (Dim)
    else if (!is_idle_2min_triggered && elapsed >= DIM_TIMEOUT_MS) {
        display->on_idle_2min();
        is_idle_2min_triggered = true;
    }
}

// ============================================================================
// INTERRUPT HANDLERS (Keep these minimal!)
// ============================================================================

// Touch interrupt handling
volatile bool touch_interrupt_flag = false;
volatile bool touch_event_down = false;
LowLevelTouch* touch = nullptr;

// Button interrupt handling
#ifdef BUTTON_KEY0_PIN
volatile bool button_key0_pressed = false;
volatile bool button_key1_pressed = false;
#endif

/**
 * @brief Touch interrupt callback handler
 * 
 * Called automatically by hardware when INT pin changes state:
 * - Falling edge: Touch detected (INT goes LOW)
 * - Rising edge: Touch released (INT goes HIGH)
 * 
 * This runs in interrupt context, so keep it fast - just set a flag
 * 
 * @param gpio GPIO pin number that triggered the interrupt
 * @param events Event mask (GPIO_IRQ_EDGE_FALL and/or GPIO_IRQ_EDGE_RISE)
 */
void touch_interrupt_handler(uint gpio, uint32_t events) {
    // Set flag to indicate touch event occurred
    // Main loop will handle the actual touch reading
    touch_interrupt_flag = true;
    
    // Track which edge triggered (down vs up)
    if (events & GPIO_IRQ_EDGE_FALL) { 
        touch_event_down = true;
        printf("INT: FALL\n");
    }
    if (events & GPIO_IRQ_EDGE_RISE) { 
        touch_event_down = false;
        printf("INT: RISE\n");
    }
}

#ifdef BUTTON_KEY0_PIN
/**
 * @brief Button interrupt callback handler
 * 
 * Called automatically by hardware when button pins change state.
 * Buttons are active LOW (pressed = 0, released = 1) with pull-ups.
 * 
 * This runs in interrupt context, so keep it fast - just set flags.
 * 
 * @param gpio GPIO pin number that triggered the interrupt
 * @param events Event mask (GPIO_IRQ_EDGE_FALL and/or GPIO_IRQ_EDGE_RISE)
 */
void button_interrupt_handler(uint gpio, uint32_t events) {
    // Only respond to falling edge (button press)
    if (events & GPIO_IRQ_EDGE_FALL) {
        if (gpio == BUTTON_KEY0_PIN) {
            button_key0_pressed = true;
        }
#ifdef BUTTON_KEY1_PIN
        else if (gpio == BUTTON_KEY1_PIN) {
            button_key1_pressed = true;
        }
#endif
    }
}
#endif

// Screen dimensions and configuration from board_config.h
const int V_WIDTH = DISPLAY_WIDTH;
const int V_HEIGHT = DISPLAY_HEIGHT;

// Touch indicator settings
#define TOUCH_RADIUS 10

uint8_t bit_buffer[V_WIDTH * V_HEIGHT / 8];

/**
 * @brief Refresh the screen with the 1-bit buffer
 * 
 * Displays work directly with 1-bit monochrome buffers.
 * The display driver internally converts to its native format (RGB565, etc.)
 * 
 * @param buffer Pointer to 1-bit framebuffer (width*height/8 bytes)
 * @param display Pointer to display abstraction layer
 */
void refresh_screen(const uint8_t *buffer, LowLevelDisplay* display) {
    display->draw_buffer(buffer);
    display->refresh();
}

// ============================================================================
// INPUT PROCESSING
// ============================================================================





// ============================================================================
// MAIN PROGRAM
// ============================================================================




int main()
{
    // Initialize standard I/O for debugging with timeout
    // This prevents hanging when USB is not connected
    stdio_init_all();
    sleep_ms(100);  // Wait for USB connection (if present)
    
    printf("\n=== %s Demo ===\n", BOARD_NAME);
    printf("Starting dual-core system...\n");
    
    // Create display abstraction using factory method
    // The factory handles all board-specific configuration internally
    LowLevelDisplay* display = LowLevelDisplay::create((DisplayType)DISPLAY_TYPE_SELECTED, V_WIDTH, V_HEIGHT);
    
    if (!display) {
        printf("Failed to create display!\n");
        return -1;
    }
    
    printf("Initializing display...\n");
    
    // Initialize the display
    if (!display->init()) {
        printf("Display initialization failed!\n");
        delete display;
        return -1;
    }

    // Enable dirty rectangle optimization for ST7796 displays
    if (display->get_type() == DISPLAY_TYPE_ST7796) {
        LowLevelDisplayST7796* st7796_display = static_cast<LowLevelDisplayST7796*>(display);
        st7796_display->enable_dirty_rect(true);
        printf("Dirty rectangle optimization enabled (4 quadrants: TL/TR/BL/BR split)\n");
    }

    // Launch Core 1 for display refresh handling
    printf("Launching Core 1 for display refresh...\n");
    multicore_launch_core1(core1_entry);
    sleep_ms(100);  // Give Core 1 time to start
    
    // Do a full refresh with white screen first (removes ghosting on e-paper)
    printf("Performing initial full refresh to white...\n");
    display->clear(true);  // Clear to white
    
    // For e-paper, do a full refresh to ensure clean display
    if (display->get_type() == DISPLAY_TYPE_EPAPER) {
        LowLevelDisplayEPaper* epaper = static_cast<LowLevelDisplayEPaper*>(display);
        epaper->full_refresh();  // Full refresh removes ghosting
        printf("Full refresh complete\n");
    } else {
        refresh_screen(bit_buffer, display);  // For TFT, just refresh normally
    }
    
    // Now clear to black for drawing
    display->clear(false);  // Clear to black

    // Initialize renderer and GUI system
    LowLevelRenderer renderer(bit_buffer, V_WIDTH, V_HEIGHT);
    renderer.set_font(&font_homespun_obj);
    LowLevelGUI gui = LowLevelGUI(&renderer, font_homespun_obj);
    
    // Initialize touch screen using abstraction FIRST (before InputManager needs it)
    touch = LowLevelTouch::create((TouchType)TOUCH_TYPE_SELECTED, V_WIDTH, V_HEIGHT,
                                                 TOUCH_SWAP_XY, TOUCH_INVERT_X, TOUCH_INVERT_Y);
    
    if (touch) {
        printf("Touch initialized successfully\n");
        
        // Set up interrupt-driven touch detection
        printf("Setting up touch interrupt callback...\n");
        touch->set_interrupt_callback(touch_interrupt_handler);
        printf("Touch interrupt enabled on INT pin (falling and rising edges)\n");
        
        // Run communication test if available
        // Note: Commented out as it may hang on some hardware configurations
        printf("\nRunning touch reliability test...\n");
        touch->test_communication();
        printf("...\n");
    } else {
        printf("Touch initialization failed or not configured\n");
    }
    
    // Initialize game configuration
    GameConfig config = {
        .touch_debounce_ms = 10,
        .button_debounce_ms = 20,
        .enable_gestures = true,
        .enable_continuous_draw = true,
        .debug_verbose = false
    };
    
    // Create InputManager for processing inputs (touch must be initialized first!)
    InputManager input_manager(touch, &config);
    
    // Create GameLauncher
    GameLauncher launcher(V_WIDTH, V_HEIGHT, &renderer, &gui, &input_manager);
    
    // Register available games
    launcher.register_game("Tic-Tac-Toe", "Classic 2-player game",
        [](uint16_t w, uint16_t h, LowLevelRenderer* r, LowLevelGUI* g, InputManager* im) -> Game* {
            return new TicTacToeGame(w, h, r, g, im);
        });

    launcher.register_game("Monopoly", "Classic property trading game",
        [](uint16_t w, uint16_t h, LowLevelRenderer* r, LowLevelGUI* g, InputManager* im) -> Game* {
            // For Feather TFT (480x320), reduce width to 430 to make room for sidebar buttons
            uint16_t game_w = (w == 480) ? 430 : w;
            return new MonopolyGame(game_w, h, r, g, im);
        });
    
    launcher.register_game("Demo Game", "Simple test game",
        [](uint16_t w, uint16_t h, LowLevelRenderer* r, LowLevelGUI* g, InputManager* im) -> Game* {
            return new DemoGame(w, h, r, g, im);
        });
    
    // Initialize SD card and mount filesystem
    printf("\nInitializing SD card...\n");
    bool sd_available = sd_card_init_with_board_config();
    if (sd_available) {
        printf("SD card initialized successfully\n");
        
        // Mount FatFS filesystem
        static FATFS fs;
        FRESULT res = f_mount(&fs, "0:", 1);  // Mount drive 0 immediately
        if (res == FR_OK) {
            printf("FatFS mounted successfully\n");
            
            // Register Lua games from SD card /games directory
            printf("Scanning for Lua games on SD card...\n");
            int lua_games_found = LuaGameLoader::register_all_games(&launcher);
            printf("Found %d Lua game(s)\n", lua_games_found);
        } else {
            printf("FatFS mount failed (error %d) - SD card may not be formatted\n", res);
        }
        
        // Restore display SPI speed (SD card shares same SPI bus)
        // SD card init sets SPI to 12.5 MHz, but display needs 32 MHz for fast refresh
        spi_set_baudrate(DISPLAY_SPI_PORT, SPI_BAUDRATE);
        printf("Display SPI speed restored to %d MHz\n", SPI_BAUDRATE / 1000000);
    } else {
        printf("SD card not available - skipping Lua game scan\n");
    }
    
    // Draw launcher menu
    launcher.draw();
    
    // Refresh the screen with the launcher menu (async on Core 1)
    refresh_screen_async(bit_buffer, display);
    printf("Initial screen refresh queued on Core 1\n");
    
#ifdef BUTTON_KEY0_PIN
    // Initialize hardware buttons (e-ink board only)
    printf("\nInitializing hardware buttons...\n");
    
    // Initialize KEY0 button
    gpio_init(BUTTON_KEY0_PIN);
    gpio_set_dir(BUTTON_KEY0_PIN, GPIO_IN);
    gpio_pull_up(BUTTON_KEY0_PIN);  // Active LOW with pull-up
    printf("  KEY0 initialized on GP%d (active LOW)\n", BUTTON_KEY0_PIN);
    
#ifdef BUTTON_KEY1_PIN
    // Initialize KEY1 button
    gpio_init(BUTTON_KEY1_PIN);
    gpio_set_dir(BUTTON_KEY1_PIN, GPIO_IN);
    gpio_pull_up(BUTTON_KEY1_PIN);  // Active LOW with pull-up
    printf("  KEY1 initialized on GP%d (active LOW)\n", BUTTON_KEY1_PIN);
#endif
    
    // Enable interrupts on falling edge (button press)
    gpio_set_irq_enabled_with_callback(BUTTON_KEY0_PIN, 
                                       GPIO_IRQ_EDGE_FALL, 
                                       true, 
                                       &button_interrupt_handler);
#ifdef BUTTON_KEY1_PIN
    gpio_set_irq_enabled(BUTTON_KEY1_PIN, GPIO_IRQ_EDGE_FALL, true);
#endif
    
    printf("Button interrupts enabled (falling edge = press)\n");
#endif
    
    // ========================================================================
    // REACTIVE GAME LOOP WITH DUAL-CORE REFRESH
    // ========================================================================
    // Core 0 (this loop): Handles input and game logic - stays responsive
    // Core 1: Handles display refresh - can take 1-2 seconds for e-ink
    // 
    // The loop sleeps until an interrupt occurs, then:
    // 1. Process input (button or touch)
    // 2. Update game state based on input
    // 3. Queue refresh on Core 1 (non-blocking)
    // This keeps Core 0 responsive even during slow e-ink refreshes
    // ========================================================================
    
    uint32_t last_touch_time = 0;
    bool pending_refresh = false;  // Track if we have a pending refresh
    
    // Initialize last interaction time to current time
    last_interaction_time = to_ms_since_boot(get_absolute_time());
    global_display = display;
    
    // Set up repeating alarm to periodically check dimming status
    // This wakes the CPU from __wfi() every DIM_CHECK_INTERVAL_MS
    add_alarm_in_ms(DIM_CHECK_INTERVAL_MS, dim_check_alarm_callback, nullptr, true);
    
    if (display->get_type() == DISPLAY_TYPE_ST7796) {
        printf("Power saving: Dim at %d min, Sleep at %d min\n", 
               DIM_TIMEOUT_MS / 60000, SLEEP_TIMEOUT_MS / 60000);
    } else {
        printf("Power saving: Sleep at %d min\n", SLEEP_TIMEOUT_MS / 60000);
    }
    printf("Dimming check timer set to %d seconds\n", DIM_CHECK_INTERVAL_MS / 1000);
    
    printf("\nEntering reactive game loop (Core 0 - input & logic)\n");
    printf("Display refreshes handled by Core 1\n");
    printf("Frame rate limited to 30 FPS (33.3ms per frame)\n\n");

    Game* current_game = nullptr;
    uint32_t game_start_time = 0;

    // Frame rate limiting (30 FPS = 33.33ms per frame)
    const uint32_t TARGET_FRAME_TIME_MS = 33;  // 1000ms / 30fps ≈ 33ms
    uint32_t last_frame_time = 0;

    while (1) {
        // Determine if we should sleep or stay awake for updates
        bool stay_awake = false;
        if (pending_refresh) stay_awake = true;
        
        if (launcher.is_game_selected()) {
            Game* g = launcher.get_selected_game();
            if (g && g->wants_frame_updates()) {
                stay_awake = true;
            }
        }

        if (!stay_awake) {
            // Sleep until interrupt wakes us up (very power efficient!)
            __wfi();  // Wait For Interrupt - CPU sleeps until any interrupt occurs
        }
        
        InputEvent input = {INPUT_NONE, 0, 0, 0, 0, 0, false};
        bool needs_refresh = false;
        
        // 1. Process button input first (higher priority)
        input = input_manager.process_button_input();
        
        // 2. Process touch input (if no button was pressed)
        if (!input.valid) {
            input = input_manager.process_touch_input(&last_touch_time);
            // if debugging enabled, print touch event
            if (input.valid && config.debug_verbose) {
                printf("Touch Event: type=%d, x=%d, y=%d, pressure=%d, gesture=0x%02X\n",
                       input.type, input.x, input.y, input.pressure, input.gesture_code);
            }
        }
        
        // 3. Process input based on current state
        if (input.valid) {
            // Record user interaction for dimming timer
            record_user_interaction(display);
            
            // if debugging enabled, print input event
            if (config.debug_verbose) {
                printf("Input Event: type=%d, x=%d, y=%d, gesture=0x%02X, button=%d, pressure=%d\n",
                       input.type, input.x, input.y, input.gesture_code,
                       input.button_id, input.pressure);
            }

            if (launcher.is_game_selected()) {
                // In game mode - process game input
                current_game = launcher.get_selected_game();
                needs_refresh = current_game->update(input);
                
                // Check if game wants to exit
                if (current_game->wants_to_exit()) {
                    printf("Game requested exit - returning to launcher\n");
                    launcher.reset();
                    needs_refresh = true;
                    // Force full clear for clean transition
                    display->clear(false);
                    if (display->get_type() == DISPLAY_TYPE_EPAPER) {
                        LowLevelDisplayEPaper* epaper = static_cast<LowLevelDisplayEPaper*>(display);
                        epaper->full_refresh();
                    }
                }
                
                // Check if player wants to exit (hold for 2+ seconds or special gesture)
                // For now, we'll add a simple long-press detection
                if (input.type == INPUT_TOUCH_DOWN) {
                    // Record start time on first touch
                    if (game_start_time == 0) {
                        game_start_time = to_ms_since_boot(get_absolute_time());
                    }
                } else if (input.type == INPUT_TOUCH_UP) {
                    uint32_t now = to_ms_since_boot(get_absolute_time());
                    if (game_start_time > 0 && (now - game_start_time) > 10000) {
                        // Long press detected - return to menu
                        printf("Long press detected - returning to launcher\n");
                        launcher.reset();
                        needs_refresh = true;
                        // Force full clear for clean transition
                        display->clear(false);
                        if (display->get_type() == DISPLAY_TYPE_EPAPER) {
                            LowLevelDisplayEPaper* epaper = static_cast<LowLevelDisplayEPaper*>(display);
                            epaper->full_refresh();
                        }
                    }
                    game_start_time = 0;
                }
            } else {
                // In launcher mode - process menu input
                bool game_selected = launcher.update(input);
                if (game_selected) {
                    printf("Game launched successfully\n");
                    game_start_time = 0;
                    // Force full clear for clean transition to game
                    display->clear(false);
                    // if (display->get_type() == DISPLAY_TYPE_EPAPER) {
                    //     LowLevelDisplayEPaper* epaper = static_cast<LowLevelDisplayEPaper*>(display);
                    //     epaper->full_refresh();
                    // }
                }
                needs_refresh = true;
            }
        } 
        
        if (launcher.is_game_selected()) {
            // No input, but check if game wants continuous updates
            current_game = launcher.get_selected_game();
            if (current_game->wants_frame_updates()) {
                // Only send frame tick if we're ready to draw the next frame
                if (!is_refresh_in_progress()) {
                    InputEvent frame_tick = {INPUT_FRAME_TICK, 0, 0, 0, 0, 0, true};
                    needs_refresh = current_game->update(frame_tick) || needs_refresh;
                }
            }
        }
        
        // 4. Redraw and queue async refresh on Core 1 (with 30 FPS limiting)
        if (needs_refresh || pending_refresh) {
            // Check frame rate limiting
            uint32_t current_time = to_ms_since_boot(get_absolute_time());
            uint32_t time_since_last_frame = current_time - last_frame_time;

            // Only proceed if enough time has passed since last frame
            if (time_since_last_frame >= TARGET_FRAME_TIME_MS) {
                // Only draw if Core 1 is finished with the buffer
                if (!is_refresh_in_progress()) {
                    // Clear buffer and redraw entire UI with updated state
                    memset(bit_buffer, 0, V_WIDTH * V_HEIGHT / 8);

                    if (launcher.is_game_selected()) {
                        current_game = launcher.get_selected_game();
                        current_game->draw();
                    } else {
                        launcher.draw();
                    }

                    // Request async refresh (non-blocking - handled by Core 1)
                    bool refresh_started = refresh_screen_async(bit_buffer, display);

                    if (refresh_started) {
                        pending_refresh = false;  // Refresh queued successfully
                        last_frame_time = current_time;  // Update frame time
                    } else {
                        pending_refresh = true;
                    }
                } else {
                    pending_refresh = true;
                }
            } else {
                // Frame rate limit: skip this frame, wait for next opportunity
                // Sleep for the remaining time to reach target frame time
                uint32_t remaining_time = TARGET_FRAME_TIME_MS - time_since_last_frame;
                if (remaining_time > 1) {
                    sleep_ms(remaining_time - 1);  // -1 to account for overhead
                }
            }
        }
        
        // 5. Check if display should be dimmed due to inactivity
        // This flag is set by timer alarm every DIM_CHECK_INTERVAL_MS
        if (dim_check_flag) {
            dim_check_flag = false;
            check_and_apply_dimming(display);
        }
    }
    
    return 0;
}
