/**
 * @file Breakout.cpp
 * @brief Breakout arcade game implementation for Tactility (Arkanoid-style)
 */
#include "Breakout.h"
#include "SfxEngine.h"

#include <cstdio>
#include <cmath>
#include <tt_lvgl_toolbar.h>
#include <tt_preferences.h>
#include <esp_random.h>
#include <tt_lvgl_keyboard.h>

#include <tactility/lvgl_module.h>
#include <tactility/lvgl_fonts.h>

constexpr auto* TAG = "Breakout";

static constexpr const char* PREF_NAMESPACE = "Breakout";
static constexpr const char* PREF_HIGH_SCORE = "high";
static constexpr const char* PREF_SOUND = "sound";

// Persistent state (file-scope)
static int32_t highScore = 0;
static bool soundEnabled = true;

// Normal brick colors (8 colors, index 0-7)
static const lv_palette_t BRICK_COLORS[] = {
    LV_PALETTE_PURPLE,     // 50 Points
    LV_PALETTE_ORANGE,     // 60 Points
    LV_PALETTE_CYAN,       // 70 Points
    LV_PALETTE_GREEN,      // 80 Points
    LV_PALETTE_RED,        // 90 Points
    LV_PALETTE_BLUE,       // 100 Points
    LV_PALETTE_PINK,       // 110 Points
    LV_PALETTE_YELLOW      // 120 Points
};

// Power-up capsule colors and labels
static const uint32_t CAPSULE_COLORS[] = {
    0xFF0000,  // Laser - Red
    0x0088FF,  // Extend - Blue
    0x00CC00,  // Catch - Green
    0xFF8800,  // Slow - Orange
    0xFF44AA,  // BreakOut - Pink
    0x00DDDD,  // Split - Cyan
    0xAAAAAA   // ExtraLife - Grey
};
static const char* CAPSULE_LETTERS[] = { "L", "E", "C", "S", "B", "D", "+" };

// Capsule drop chance (out of 100)
static constexpr int CAPSULE_DROP_CHANCE = 15;
// Catch auto-release ticks (3 seconds at 40fps)
static constexpr int CATCH_AUTO_RELEASE_TICKS = 120;
// Slow recovery duration ticks (5 seconds)
static constexpr int SLOW_RECOVERY_TICKS = 200;
// Laser cooldown ticks between shots
static constexpr int LASER_COOLDOWN_TICKS = 12;
// Amber (Gold) bricks
static constexpr int INDESTRUCTIBLE_HITS = 999;

static void loadSettings() {
    PreferencesHandle prefs = tt_preferences_alloc(PREF_NAMESPACE);
    if (prefs) {
        tt_preferences_opt_int32(prefs, PREF_HIGH_SCORE, &highScore);
        int32_t snd = 1;
        tt_preferences_opt_int32(prefs, PREF_SOUND, &snd);
        soundEnabled = (snd != 0);
        tt_preferences_free(prefs);
    }
}

static void saveHighScore(int32_t score) {
    if (score <= highScore) return;
    highScore = score;
    PreferencesHandle prefs = tt_preferences_alloc(PREF_NAMESPACE);
    if (prefs) {
        tt_preferences_put_int32(prefs, PREF_HIGH_SCORE, score);
        tt_preferences_free(prefs);
    }
}

static void saveSoundSetting(bool enabled) {
    soundEnabled = enabled;
    PreferencesHandle prefs = tt_preferences_alloc(PREF_NAMESPACE);
    if (prefs) {
        tt_preferences_put_int32(prefs, PREF_SOUND, enabled ? 1 : 0);
        tt_preferences_free(prefs);
    }
}

// Simple seeded random for procedural levels
static uint32_t levelRng(uint32_t& seed) {
    seed = seed * 1103515245 + 12345;
    return (seed >> 16) & 0x7FFF;
}

// ── UI Creation ──────────────────────────────────────────────

static uint32_t getToolbarHeight(UiDensity uiDensity) {
    if (uiDensity == LVGL_UI_DENSITY_COMPACT) {
        return lvgl_get_text_font_height(FONT_SIZE_DEFAULT) * 1.4f;
    } else {
        return lvgl_get_text_font_height(FONT_SIZE_LARGE) * 2.2f;
    }
}

static uint32_t getActionIconPadding(UiDensity uiDensity) {
    auto toolbar_height = getToolbarHeight(uiDensity);
    return (uiDensity != LVGL_UI_DENSITY_COMPACT) ? (uint32_t)(toolbar_height * 0.2f) : 8;
}

void Breakout::onShow(AppHandle appHandle, lv_obj_t* parent) {
    lv_obj_remove_flag(parent, LV_OBJ_FLAG_SCROLLABLE);
    lv_obj_set_flex_flow(parent, LV_FLEX_FLOW_COLUMN);
    lv_obj_set_style_pad_all(parent, 0, 0);
    lv_obj_set_style_pad_row(parent, 0, 0);

    // Load settings on first show
    if (needsInit) loadSettings();

    // Start sfx engine
    if (!sfxEngine) {
        sfxEngine = new SfxEngine();
        sfxEngine->start();
        sfxEngine->applyVolumePreset(SfxEngine::VolumePreset::Quiet);
        sfxEngine->setEnabled(soundEnabled);
    }

    // Toolbar
    lv_obj_t* toolbar = tt_lvgl_toolbar_create_for_app(parent, appHandle);

    // Score wrapper in toolbar
    lv_obj_t* scoreWrap = lv_obj_create(toolbar);
    lv_obj_set_size(scoreWrap, LV_SIZE_CONTENT, LV_PCT(100));
    lv_obj_set_style_pad_top(scoreWrap, 4, 0);
    lv_obj_set_style_pad_bottom(scoreWrap, 0, 0);
    lv_obj_set_style_pad_left(scoreWrap, 0, 0);
    lv_obj_set_style_pad_right(scoreWrap, 10, 0);
    lv_obj_set_style_border_width(scoreWrap, 0, 0);
    lv_obj_set_style_bg_opa(scoreWrap, 0, 0);
    lv_obj_remove_flag(scoreWrap, LV_OBJ_FLAG_SCROLLABLE);

    scoreLabel = lv_label_create(scoreWrap);
    lv_obj_set_style_text_font(scoreLabel, lv_font_get_default(), 0);
    lv_obj_set_style_text_color(scoreLabel, lv_palette_main(LV_PALETTE_AMBER), 0);
    lv_obj_align(scoreLabel, LV_ALIGN_CENTER, 0, 0);

    // Lives wrapper in toolbar
    lv_obj_t* livesWrap = lv_obj_create(toolbar);
    lv_obj_set_size(livesWrap, LV_SIZE_CONTENT, LV_PCT(100));
    lv_obj_set_style_pad_top(livesWrap, 4, 0);
    lv_obj_set_style_pad_bottom(livesWrap, 0, 0);
    lv_obj_set_style_pad_left(livesWrap, 0, 0);
    lv_obj_set_style_pad_right(livesWrap, 10, 0);
    lv_obj_set_style_border_width(livesWrap, 0, 0);
    lv_obj_set_style_bg_opa(livesWrap, 0, 0);
    lv_obj_remove_flag(livesWrap, LV_OBJ_FLAG_SCROLLABLE);

    livesLabel = lv_label_create(livesWrap);
    lv_obj_set_style_text_font(livesLabel, lv_font_get_default(), 0);
    lv_obj_set_style_text_color(livesLabel, lv_palette_main(LV_PALETTE_RED), 0);
    lv_obj_align(livesLabel, LV_ALIGN_CENTER, 0, 0);

    auto ui_density = lvgl_get_ui_density();
    auto toolbar_height = getToolbarHeight(ui_density);
    auto icon_padding = getActionIconPadding(ui_density);

    // Toolbar buttons wrapper
    lv_obj_t* btnsWrapper = lv_obj_create(toolbar);
    lv_obj_set_width(btnsWrapper, LV_SIZE_CONTENT);
    lv_obj_set_flex_flow(btnsWrapper, LV_FLEX_FLOW_ROW);
    lv_obj_set_style_pad_all(btnsWrapper, icon_padding / 2, LV_STATE_DEFAULT);
    lv_obj_set_style_border_width(btnsWrapper, 0, LV_STATE_DEFAULT);
    lv_obj_set_style_bg_opa(btnsWrapper, 0, LV_STATE_DEFAULT);

    // Pause button
    lv_obj_t* pauseBtn = lv_btn_create(btnsWrapper);
    lv_obj_set_size(pauseBtn, toolbar_height - icon_padding, toolbar_height - icon_padding);
    lv_obj_set_style_pad_all(pauseBtn, 0, LV_STATE_DEFAULT);
    lv_obj_align(pauseBtn, LV_ALIGN_CENTER, 0, 0);
    lv_obj_add_event_cb(pauseBtn, onPauseClicked, LV_EVENT_CLICKED, this);

    lv_obj_t* pauseIcon = lv_label_create(pauseBtn);
    lv_label_set_text(pauseIcon, LV_SYMBOL_PAUSE);
    lv_obj_align(pauseIcon, LV_ALIGN_CENTER, 0, 0);

    // Sound toggle button
    lv_obj_t* soundBtn = lv_btn_create(btnsWrapper);
    lv_obj_set_size(soundBtn, toolbar_height - icon_padding, toolbar_height - icon_padding);
    lv_obj_set_style_pad_all(soundBtn, 0, LV_STATE_DEFAULT);
    lv_obj_align(soundBtn, LV_ALIGN_CENTER, 0, 0);
    lv_obj_add_event_cb(soundBtn, onSoundToggled, LV_EVENT_CLICKED, this);

    soundBtnIcon = lv_label_create(soundBtn);
    lv_obj_align(soundBtnIcon, LV_ALIGN_CENTER, 0, 0);
    updateSoundIcon();

    // Screen size detection (The Book)
    lv_coord_t screenW = lv_display_get_horizontal_resolution(nullptr);
    lv_coord_t screenH = lv_display_get_vertical_resolution(nullptr);
    bool isSmall = (screenW < 280 || screenH < 180);
    bool isXLarge = (screenW >= 600);

    // Scaled dimensions
    cols = isSmall ? 8 : (isXLarge ? 12 : 10);
    rows = isSmall ? 3 : (isXLarge ? 5 : 4);
    brickW = isSmall ? 24 : (isXLarge ? 56 : 28);
    brickH = isSmall ? 8 : (isXLarge ? 18 : 10);
    brickGap = isSmall ? 2 : (isXLarge ? 4 : 2);
    ballSize = isSmall ? 6 : (isXLarge ? 14 : 8);
    paddleW = isSmall ? 40 : (isXLarge ? 100 : 54);
    paddleH = isSmall ? 6 : (isXLarge ? 14 : 8);
    baseBallSpeed = isSmall ? 2.0f : (isXLarge ? 4.0f : 2.5f);
    paddleSpeed = isSmall ? 16.0f : (isXLarge ? 36.0f : 24.0f);
    int paddleMargin = isSmall ? 2 : (isXLarge ? 8 : 4);
    int brickTopPad = isSmall ? 4 : (isXLarge ? 12 : 8);

    // Capsule dimensions
    capsuleW = isSmall ? 16 : (isXLarge ? 36 : 22);
    capsuleH = isSmall ? 8 : (isXLarge ? 16 : 12);
    capsuleFallSpeed = isSmall ? 1.2f : (isXLarge ? 2.5f : 1.8f);

    // Laser dimensions
    laserW = isSmall ? 2 : (isXLarge ? 4 : 3);
    laserH = isSmall ? 6 : (isXLarge ? 12 : 8);
    laserSpeed = isSmall ? 4.0f : (isXLarge ? 8.0f : 6.0f);

    // Store original paddle width for extend reset
    originalPaddleW = paddleW;

    // Ball speed includes level scaling
    ballSpeed = baseBallSpeed + (level - 1) * 0.3f;

    // Game area
    gameArea = lv_obj_create(parent);
    lv_obj_set_width(gameArea, LV_PCT(100));
    lv_obj_set_flex_grow(gameArea, 1);
    lv_obj_set_style_bg_color(gameArea, lv_color_hex(0x0a0a1e), 0);
    lv_obj_set_style_bg_opa(gameArea, LV_OPA_COVER, 0);
    lv_obj_set_style_border_width(gameArea, 0, 0);
    lv_obj_set_style_pad_all(gameArea, 0, 0);
    lv_obj_set_style_radius(gameArea, 0, 0);
    lv_obj_remove_flag(gameArea, LV_OBJ_FLAG_SCROLLABLE);
    lv_obj_add_flag(gameArea, LV_OBJ_FLAG_CLICKABLE);

    // Force layout to get accurate game area dimensions
    lv_obj_update_layout(parent);
    areaW = lv_obj_get_content_width(gameArea);
    areaH = lv_obj_get_content_height(gameArea);
    paddleYPos = areaH - paddleH - paddleMargin;

    // Calculate brick layout (centered horizontally)
    int totalBrickW = cols * brickW + (cols - 1) * brickGap;
    brickOffsetX = (areaW - totalBrickW) / 2;
    brickOffsetY = brickTopPad;

    // Create bricks
    createBricks();

    // Create paddle
    paddle = lv_obj_create(gameArea);
    lv_obj_set_size(paddle, paddleW, paddleH);
    lv_obj_set_style_bg_color(paddle, lv_palette_main(LV_PALETTE_LIGHT_BLUE), 0);
    lv_obj_set_style_border_width(paddle, 0, 0);
    lv_obj_set_style_pad_all(paddle, 0, 0);
    lv_obj_set_style_radius(paddle, 2, 0);
    lv_obj_remove_flag(paddle, LV_OBJ_FLAG_SCROLLABLE);

    // Create balls (primary + extras for split)
    for (int i = 0; i < MAX_BALLS; i++) {
        balls[i].obj = lv_obj_create(gameArea);
        lv_obj_set_size(balls[i].obj, ballSize, ballSize);
        lv_obj_set_style_bg_color(balls[i].obj, lv_color_white(), 0);
        lv_obj_set_style_border_width(balls[i].obj, 0, 0);
        lv_obj_set_style_pad_all(balls[i].obj, 0, 0);
        lv_obj_set_style_radius(balls[i].obj, LV_RADIUS_CIRCLE, 0);
        lv_obj_remove_flag(balls[i].obj, LV_OBJ_FLAG_SCROLLABLE);
        if (i > 0) {
            lv_obj_add_flag(balls[i].obj, LV_OBJ_FLAG_HIDDEN);
        }
    }

    // Create capsule objects (pre-created, hidden)
    createCapsuleObjs();

    // Create laser objects (pre-created, hidden)
    createLaserObjs();

    // BreakOut exit indicator at paddle level (hidden by default)
    int exitH = paddleH * 3;
    exitIndicator = lv_obj_create(gameArea);
    lv_obj_set_size(exitIndicator, 6, exitH);
    lv_obj_set_style_bg_color(exitIndicator, lv_color_hex(0xFF44AA), 0);
    lv_obj_set_style_bg_opa(exitIndicator, LV_OPA_COVER, 0);
    lv_obj_set_style_border_width(exitIndicator, 0, 0);
    lv_obj_set_style_radius(exitIndicator, 0, 0);
    lv_obj_remove_flag(exitIndicator, LV_OBJ_FLAG_SCROLLABLE);
    lv_obj_set_pos(exitIndicator, areaW - 6, paddleYPos - exitH / 2 + paddleH / 2);
    lv_obj_add_flag(exitIndicator, LV_OBJ_FLAG_HIDDEN);

    // Message overlay (centered in game area)
    messageLabel = lv_label_create(gameArea);
    lv_obj_set_style_text_color(messageLabel, lv_color_white(), 0);
    lv_obj_set_style_text_align(messageLabel, LV_TEXT_ALIGN_CENTER, 0);
    lv_obj_center(messageLabel);

    // Initialize or restore
    if (needsInit) {
        paddleX = (areaW - paddleW) / 2.0f;
        startGame();
        needsInit = false;
    } else {
        // Restore visual positions from saved state
        // Re-apply extended paddle width if still active
        if (extendActive && paddle) lv_obj_set_width(paddle, paddleW);
        lv_obj_set_pos(paddle, (int)paddleX, paddleYPos);
        for (int i = 0; i < MAX_BALLS; i++) {
            if (balls[i].active && balls[i].obj) {
                lv_obj_set_pos(balls[i].obj, (int)balls[i].x, (int)balls[i].y);
                lv_obj_clear_flag(balls[i].obj, LV_OBJ_FLAG_HIDDEN);
            }
        }
    // Restore falling capsules
    for (int i = 0; i < MAX_CAPSULES; i++) {
        if (capsules[i].active && capsuleObjs[i]) {
            lv_obj_set_pos(capsuleObjs[i], (int)capsules[i].x, (int)capsules[i].y);
            lv_obj_clear_flag(capsuleObjs[i], LV_OBJ_FLAG_HIDDEN);
        }
    }
    // Restore exit indicator
    if (exitOpen && exitIndicator) lv_obj_clear_flag(exitIndicator, LV_OBJ_FLAG_HIDDEN);
        updateScoreDisplay();
        updateMessage();
    }

    // Input handlers
    lv_obj_add_event_cb(gameArea, onPressed, LV_EVENT_PRESSING, this);
    lv_obj_add_event_cb(gameArea, onClicked, LV_EVENT_SHORT_CLICKED, this);
    lv_obj_add_event_cb(gameArea, onKey, LV_EVENT_KEY, this);
    lv_obj_add_event_cb(gameArea, onReenterKeyMode, LV_EVENT_CLICKED, this);

    // Keyboard focus - explicit enter/exit, no focus/defocus handlers
    lv_group_t* group = lv_group_get_default();
    if (group) {
        lv_group_add_obj(group, gameArea);
        lv_group_focus_obj(gameArea);
        lv_group_set_editing(group, true);
    }

    // Start game timer
    gameTimer = lv_timer_create(onTick, TICK_MS, this);
}

void Breakout::onHide(AppHandle appHandle) {
    if (gameTimer) {
        lv_timer_delete(gameTimer);
        gameTimer = nullptr;
    }
    if (gameArea) {
        lv_group_t* group = lv_group_get_default();
        if (group) lv_group_set_editing(group, false);
        lv_group_remove_obj(gameArea);
    }
    gameArea = nullptr;
    paddle = nullptr;
    for (int i = 0; i < MAX_BRICKS; i++) bricks[i] = nullptr;
    for (int i = 0; i < MAX_BALLS; i++) balls[i].obj = nullptr;
    for (int i = 0; i < MAX_CAPSULES; i++) {
        capsuleObjs[i] = nullptr;
        capsuleLabels[i] = nullptr;
    }
    for (int i = 0; i < MAX_LASERS; i++) lasers[i].obj = nullptr;
    exitIndicator = nullptr;
    scoreLabel = nullptr;
    livesLabel = nullptr;
    messageLabel = nullptr;
    soundBtnIcon = nullptr;

    // Clean up sfx engine
    if (sfxEngine) {
        sfxEngine->stop();
        delete sfxEngine;
        sfxEngine = nullptr;
    }
}

// ── Capsule & Laser Object Creation ─────────────────────────

void Breakout::createCapsuleObjs() {
    for (int i = 0; i < MAX_CAPSULES; i++) {
        capsuleObjs[i] = lv_obj_create(gameArea);
        lv_obj_set_size(capsuleObjs[i], capsuleW, capsuleH);
        lv_obj_set_style_border_width(capsuleObjs[i], 1, 0);
        lv_obj_set_style_border_color(capsuleObjs[i], lv_color_white(), 0);
        lv_obj_set_style_pad_all(capsuleObjs[i], 0, 0);
        lv_obj_set_style_radius(capsuleObjs[i], 3, 0);
        lv_obj_remove_flag(capsuleObjs[i], LV_OBJ_FLAG_SCROLLABLE);
        lv_obj_remove_flag(capsuleObjs[i], LV_OBJ_FLAG_CLICKABLE);
        lv_obj_add_flag(capsuleObjs[i], LV_OBJ_FLAG_HIDDEN);

        capsuleLabels[i] = lv_label_create(capsuleObjs[i]);
        lv_obj_set_style_text_color(capsuleLabels[i], lv_color_white(), 0);
        lv_obj_center(capsuleLabels[i]);
    }
}

void Breakout::createLaserObjs() {
    for (int i = 0; i < MAX_LASERS; i++) {
        lasers[i].obj = lv_obj_create(gameArea);
        lv_obj_set_size(lasers[i].obj, laserW, laserH);
        lv_obj_set_style_bg_color(lasers[i].obj, lv_color_hex(0xFF4444), 0);
        lv_obj_set_style_bg_opa(lasers[i].obj, LV_OPA_COVER, 0);
        lv_obj_set_style_border_width(lasers[i].obj, 0, 0);
        lv_obj_set_style_pad_all(lasers[i].obj, 0, 0);
        lv_obj_set_style_radius(lasers[i].obj, 0, 0);
        lv_obj_remove_flag(lasers[i].obj, LV_OBJ_FLAG_SCROLLABLE);
        lv_obj_add_flag(lasers[i].obj, LV_OBJ_FLAG_HIDDEN);
        lasers[i].active = false;
    }
}

// ── Brick Creation ───────────────────────────────────────────

void Breakout::createBricks() {
    for (int r = 0; r < rows; r++) {
        for (int c = 0; c < cols; c++) {
            int idx = r * cols + c;
            if (idx >= MAX_BRICKS) continue;
            bricks[idx] = lv_obj_create(gameArea);
            lv_obj_set_size(bricks[idx], brickW, brickH);
            lv_obj_set_style_border_width(bricks[idx], 0, 0);
            lv_obj_set_style_pad_all(bricks[idx], 0, 0);
            lv_obj_set_style_radius(bricks[idx], 2, 0);
            lv_obj_remove_flag(bricks[idx], LV_OBJ_FLAG_SCROLLABLE);
            lv_obj_remove_flag(bricks[idx], LV_OBJ_FLAG_CLICKABLE);

            int x = brickOffsetX + c * (brickW + brickGap);
            int y = brickOffsetY + r * (brickH + brickGap);
            lv_obj_set_pos(bricks[idx], x, y);
        }
    }
    refreshBricks();
}

void Breakout::setupLevelPattern() {
    int total = cols * rows;
    int numPatterns = 12;

    // Initialize all bricks
    for (int i = 0; i < total; i++) {
        brickAlive[i] = true;
        brickType[i] = BrickType::Normal;
        brickHits[i] = 1;
    }

    int colorOffset = (level - 1) % 8;
    for (int r = 0; r < rows; r++) {
        for (int c = 0; c < cols; c++) {
            brickColorIndex[r * cols + c] = (r + colorOffset) % 8;
        }
    }

    if (level <= numPatterns) {
        // Hardcoded patterns
        int pattern = (level - 1) % numPatterns;
        switch (pattern) {
            case 0: // Full grid
                break;
            case 1: // Checkerboard
                for (int r = 0; r < rows; r++)
                    for (int c = 0; c < cols; c++)
                        if ((r + c) % 2 != 0) brickAlive[r * cols + c] = false;
                break;
            case 2: // Diamond
                for (int r = 0; r < rows; r++)
                    for (int c = 0; c < cols; c++) {
                        int dr = std::abs(r - rows / 2);
                        int dc = std::abs(c - cols / 2);
                        if (dr + dc > (rows / 2 + cols / 4)) brickAlive[r * cols + c] = false;
                    }
                break;
            case 3: // Horizontal stripes
                for (int r = 0; r < rows; r++)
                    if (r % 2 != 0)
                        for (int c = 0; c < cols; c++) brickAlive[r * cols + c] = false;
                break;
            case 4: // Pyramid (wider at top, narrows down)
                for (int r = 0; r < rows; r++) {
                    int margin = r;
                    for (int c = 0; c < cols; c++)
                        if (c < margin || c >= cols - margin) brickAlive[r * cols + c] = false;
                }
                break;
            case 5: // Inverted V (wider at bottom)
                for (int r = 0; r < rows; r++) {
                    int margin = rows - 1 - r;
                    for (int c = 0; c < cols; c++)
                        if (c < margin || c >= cols - margin) brickAlive[r * cols + c] = false;
                }
                break;
            case 6: // Vertical stripes
                for (int r = 0; r < rows; r++)
                    for (int c = 0; c < cols; c++)
                        if (c % 2 != 0) brickAlive[r * cols + c] = false;
                break;
            case 7: // Zigzag rows
                for (int r = 0; r < rows; r++)
                    for (int c = 0; c < cols; c++) {
                        int shift = (r % 2 == 0) ? 0 : 2;
                        if ((c + shift) % 4 >= 2) brickAlive[r * cols + c] = false;
                    }
                break;
            case 8: // Alternating blocks (2x2 groups)
                for (int r = 0; r < rows; r++)
                    for (int c = 0; c < cols; c++) {
                        int br = r / 2;
                        int bc = c / 2;
                        if ((br + bc) % 2 != 0) brickAlive[r * cols + c] = false;
                    }
                break;
            case 9: // Double diamond
                for (int r = 0; r < rows; r++)
                    for (int c = 0; c < cols; c++) {
                        int dr = std::abs(r - rows / 2);
                        int leftC = cols / 4;
                        int rightC = cols * 3 / 4;
                        int dcLeft = std::abs(c - leftC);
                        int dcRight = std::abs(c - rightC);
                        int minDc = dcLeft < dcRight ? dcLeft : dcRight;
                        if (dr + minDc > (rows / 2 + 1)) brickAlive[r * cols + c] = false;
                    }
                break;
            case 10: // Border frame (sparser - saved for later levels)
                for (int r = 1; r < rows - 1; r++)
                    for (int c = 1; c < cols - 1; c++)
                        brickAlive[r * cols + c] = false;
                break;
            case 11: // Center cross
                for (int r = 0; r < rows; r++)
                    for (int c = 0; c < cols; c++) {
                        bool onHoriz = (r == rows / 2);
                        bool onVert = (c == cols / 2 || c == cols / 2 - 1);
                        if (!onHoriz && !onVert) brickAlive[r * cols + c] = false;
                    }
                break;
        }
    } else {
        // Procedural generation (levels 13+)
        uint32_t seed = (uint32_t)level * 2654435761u;
        int density = 50 + (level * 2);
        if (density > 85) density = 85;

        for (int i = 0; i < total; i++) {
            int roll = (int)(levelRng(seed) % 100);
            brickAlive[i] = (roll < density);
        }
        // Ensure at least some bricks exist
        int aliveCount = 0;
        for (int i = 0; i < total; i++) if (brickAlive[i]) aliveCount++;
        if (aliveCount < 5) {
            for (int i = 0; i < total && aliveCount < 8; i++) {
                if (!brickAlive[i]) { brickAlive[i] = true; aliveCount++; }
            }
        }
    }

    // Add Silver bricks (level 3+)
    if (level >= 3) {
        int silverCount = (level - 2);
        if (silverCount > rows) silverCount = rows;
        int silverHits = (level >= 7) ? 3 : 2;
        uint32_t seed = (uint32_t)level * 31337u;
        int placed = 0;
        for (int attempt = 0; attempt < total * 2 && placed < silverCount; attempt++) {
            int idx = (int)(levelRng(seed) % total);
            if (brickAlive[idx] && brickType[idx] == BrickType::Normal) {
                brickType[idx] = BrickType::Silver;
                brickHits[idx] = silverHits;
                placed++;
            }
        }
    }

    // Add Gold bricks (level 5+)
    if (level >= 5) {
        int goldCount = (level - 4) / 2;
        if (goldCount > 4) goldCount = 4;
        uint32_t seed = (uint32_t)level * 48271u;
        int placed = 0;
        for (int attempt = 0; attempt < total * 2 && placed < goldCount; attempt++) {
            int idx = (int)(levelRng(seed) % total);
            if (brickAlive[idx] && brickType[idx] == BrickType::Normal) {
                brickType[idx] = BrickType::Gold;
                brickHits[idx] = INDESTRUCTIBLE_HITS; // indestructible
                placed++;
            }
        }
    }

    // Count alive bricks (excluding Gold)
    bricksRemaining = 0;
    destroyedCount = 0;
    for (int i = 0; i < total; i++) {
        if (brickAlive[i] && brickType[i] != BrickType::Gold) bricksRemaining++;
    }
}

void Breakout::refreshBricks() {
    for (int r = 0; r < rows; r++) {
        for (int c = 0; c < cols; c++) {
            int idx = r * cols + c;
            if (!bricks[idx]) continue;

            if (!brickAlive[idx]) {
                lv_obj_add_flag(bricks[idx], LV_OBJ_FLAG_HIDDEN);
                continue;
            }

            lv_obj_clear_flag(bricks[idx], LV_OBJ_FLAG_HIDDEN);

            switch (brickType[idx]) {
                case BrickType::Normal: {
                    lv_palette_t color = BRICK_COLORS[brickColorIndex[idx]];
                    lv_obj_set_style_bg_color(bricks[idx], lv_palette_main(color), 0);
                    lv_obj_set_style_border_width(bricks[idx], 0, 0);
                    break;
                }
                case BrickType::Silver: {
                    int darken = 3 - brickHits[idx]; // more hits taken = darker
                    if (darken < 0) darken = 0;
                    if (darken > 3) darken = 3;
                    lv_obj_set_style_bg_color(bricks[idx], lv_palette_darken(LV_PALETTE_GREY, darken), 0);
                    lv_obj_set_style_border_width(bricks[idx], 1, 0);
                    lv_obj_set_style_border_color(bricks[idx], lv_color_white(), 0);
                    break;
                }
                case BrickType::Gold:
                    lv_obj_set_style_bg_color(bricks[idx], lv_palette_main(LV_PALETTE_AMBER), 0);
                    lv_obj_set_style_border_width(bricks[idx], 1, 0);
                    lv_obj_set_style_border_color(bricks[idx], lv_palette_lighten(LV_PALETTE_AMBER, 2), 0);
                    break;
            }
        }
    }
}

// ── Brick Hit Logic ──────────────────────────────────────────

int Breakout::scoreBrick(int idx) {
    switch (brickType[idx]) {
        case BrickType::Silver:
            return 50 * level;
        case BrickType::Gold:
            return 0; // can't be destroyed
        case BrickType::Normal:
        default:
            return COLOR_SCORES[brickColorIndex[idx] % 8];
    }
}

void Breakout::hitBrick(int idx) {
    if (!brickAlive[idx]) return;

    if (brickType[idx] == BrickType::Gold) {
        // Bounce but don't damage
        if (sfxEngine) sfxEngine->play(SfxId::Click);
        return;
    }

    brickHits[idx]--;
    if (brickHits[idx] <= 0) {
        // Brick destroyed
        brickAlive[idx] = false;
        if (bricks[idx]) lv_obj_add_flag(bricks[idx], LV_OBJ_FLAG_HIDDEN);
        score += scoreBrick(idx);
        if (brickType[idx] != BrickType::Gold) {
            bricksRemaining--;
            destroyedCount++;
        }

        // Speed up ball slightly every 5 bricks destroyed
        if (destroyedCount % 5 == 0) {
            ballSpeed += 0.15f;
            // Scale active ball velocities
            for (int b = 0; b < MAX_BALLS; b++) {
                if (!balls[b].active) continue;
                float curSpd = std::sqrt(balls[b].vx * balls[b].vx + balls[b].vy * balls[b].vy);
                if (curSpd > 0.01f) {
                    float scale = ballSpeed / curSpd;
                    balls[b].vx *= scale;
                    balls[b].vy *= scale;
                }
            }
        }

        updateScoreDisplay();
        if (sfxEngine) sfxEngine->play(SfxId::BrickHit);

        // Try to spawn capsule (only when single ball)
        if (activeBallCount <= 1) {
            int bx = brickOffsetX + (idx % cols) * (brickW + brickGap);
            int by = brickOffsetY + (idx / cols) * (brickH + brickGap);
            if ((int)(esp_random() % 100) < CAPSULE_DROP_CHANCE) {
                spawnCapsule((float)bx, (float)by);
            }
        }

        if (bricksRemaining <= 0) {
            winLevel();
        }
    } else {
        // Multi-hit brick took damage (Silver)
        if (bricks[idx]) {
            int darken = 3 - brickHits[idx];
            if (darken < 0) darken = 0;
            if (darken > 3) darken = 3;
            lv_obj_set_style_bg_color(bricks[idx], lv_palette_darken(LV_PALETTE_GREY, darken), 0);
        }
        if (sfxEngine) sfxEngine->play(SfxId::Click);
    }
}

// ── Game State Management ────────────────────────────────────

void Breakout::startGame() {
    score = 0;
    lives = INITIAL_LIVES;
    level = 1;
    state = GameState::Ready;
    ballSpeed = baseBallSpeed;

    clearPowerUps();
    setupLevelPattern();
    refreshBricks();

    paddleX = (areaW - paddleW) / 2.0f;
    if (paddle) lv_obj_set_pos(paddle, (int)paddleX, paddleYPos);

    resetBall();
    updateScoreDisplay();
    updateMessage();

    if (sfxEngine) sfxEngine->play(SfxId::Confirm);
}

void Breakout::nextLevel() {
    level++;

    // Speed up ball each level
    ballSpeed = baseBallSpeed + (level - 1) * 0.3f;

    clearPowerUps();
    setupLevelPattern();
    refreshBricks();

    state = GameState::Ready;
    paddleX = (areaW - paddleW) / 2.0f;
    if (paddle) lv_obj_set_pos(paddle, (int)paddleX, paddleYPos);

    resetBall();
    updateScoreDisplay();
    updateMessage();

    if (sfxEngine) sfxEngine->play(SfxId::LevelUp);
}

void Breakout::resetBall() {
    // Reset to single ball
    for (int i = 1; i < MAX_BALLS; i++) {
        balls[i].active = false;
        if (balls[i].obj) lv_obj_add_flag(balls[i].obj, LV_OBJ_FLAG_HIDDEN);
    }
    activeBallCount = 1;

    balls[0].active = true;
    balls[0].x = paddleX + paddleW / 2.0f - ballSize / 2.0f;
    balls[0].y = (float)(paddleYPos - ballSize - 2);
    balls[0].vx = 0;
    balls[0].vy = 0;
    if (balls[0].obj) {
        lv_obj_set_pos(balls[0].obj, (int)balls[0].x, (int)balls[0].y);
        lv_obj_clear_flag(balls[0].obj, LV_OBJ_FLAG_HIDDEN);
    }
}

void Breakout::launchBall() {
    if (catchActive && catchBallIndex >= 0) {
        // Release caught ball
        BallState& b = balls[catchBallIndex];
        b.vx = (esp_random() % 2 ? 1.0f : -1.0f) * ballSpeed * 0.7f;
        b.vy = -ballSpeed;
        catchBallIndex = -1;
        catchAutoReleaseTicks = 0;
        if (sfxEngine) sfxEngine->play(SfxId::Confirm);
        return;
    }

    balls[0].vx = (esp_random() % 2 ? 1.0f : -1.0f) * ballSpeed * 0.7f;
    balls[0].vy = -ballSpeed;
    state = GameState::Playing;
    updateMessage();

    if (sfxEngine) sfxEngine->play(SfxId::Confirm);
}

void Breakout::loseLife() {
    lives--;
    clearPowerUps();

    if (sfxEngine) sfxEngine->play(SfxId::Hurt);

    if (lives <= 0) {
        state = GameState::GameOver;
        for (int i = 0; i < MAX_BALLS; i++) {
            balls[i].vx = 0;
            balls[i].vy = 0;
        }
        if (sfxEngine) sfxEngine->play(SfxId::GameOver);
    } else {
        state = GameState::Ready;
        paddleX = (areaW - paddleW) / 2.0f;
        if (paddle) lv_obj_set_pos(paddle, (int)paddleX, paddleYPos);
        resetBall();
    }
    updateScoreDisplay();
    updateMessage();
    if (lives <= 0) {
        saveHighScore(score);
    }
}

void Breakout::winLevel() {
    saveHighScore(score);
    nextLevel();
}

void Breakout::togglePause() {
    if (state == GameState::Playing) {
        state = GameState::Paused;
        updateMessage();
    } else if (state == GameState::Paused) {
        state = GameState::Playing;
        updateMessage();
    }
}

// ── Power-Up System ──────────────────────────────────────────

void Breakout::spawnCapsule(float x, float y) {
    for (int i = 0; i < MAX_CAPSULES; i++) {
        if (!capsules[i].active) {
            capsules[i].active = true;
            capsules[i].x = x;
            capsules[i].y = y;

            // Random power-up type (ExtraLife rarer)
            int roll = (int)(esp_random() % 100);
            if (roll < 5) {
                capsules[i].type = PowerUpType::ExtraLife;
            } else if (roll < 18) {
                capsules[i].type = PowerUpType::Laser;
            } else if (roll < 33) {
                capsules[i].type = PowerUpType::Extend;
            } else if (roll < 48) {
                capsules[i].type = PowerUpType::Catch;
            } else if (roll < 63) {
                capsules[i].type = PowerUpType::Slow;
            } else if (roll < 78) {
                capsules[i].type = PowerUpType::Split;
            } else {
                capsules[i].type = PowerUpType::BreakOut;
            }

            int typeIdx = static_cast<int>(capsules[i].type);
            if (capsuleObjs[i]) {
                lv_obj_set_style_bg_color(capsuleObjs[i], lv_color_hex(CAPSULE_COLORS[typeIdx]), 0);
                lv_obj_set_pos(capsuleObjs[i], (int)x, (int)y);
                lv_obj_clear_flag(capsuleObjs[i], LV_OBJ_FLAG_HIDDEN);
            }
            if (capsuleLabels[i]) {
                lv_label_set_text(capsuleLabels[i], CAPSULE_LETTERS[typeIdx]);
            }
            return;
        }
    }
}

void Breakout::updateCapsules() {
    for (int i = 0; i < MAX_CAPSULES; i++) {
        if (!capsules[i].active) continue;

        capsules[i].y += capsuleFallSpeed;

        // Off screen
        if (capsules[i].y > areaH) {
            capsules[i].active = false;
            if (capsuleObjs[i]) lv_obj_add_flag(capsuleObjs[i], LV_OBJ_FLAG_HIDDEN);
            continue;
        }

        // Paddle collision
        if (capsules[i].y + capsuleH > paddleYPos &&
            capsules[i].y < paddleYPos + paddleH &&
            capsules[i].x + capsuleW > paddleX &&
            capsules[i].x < paddleX + paddleW) {
            activatePowerUp(capsules[i].type);
            capsules[i].active = false;
            if (capsuleObjs[i]) lv_obj_add_flag(capsuleObjs[i], LV_OBJ_FLAG_HIDDEN);
            continue;
        }

        if (capsuleObjs[i]) lv_obj_set_pos(capsuleObjs[i], (int)capsules[i].x, (int)capsules[i].y);
    }
}

void Breakout::activatePowerUp(PowerUpType type) {
    if (sfxEngine) sfxEngine->play(SfxId::Powerup);

    switch (type) {
        case PowerUpType::Extend:
            if (!extendActive) {
                extendActive = true;
                paddleW = (int)(originalPaddleW * 1.5f);
                int maxW = areaW / 2;
                if (paddleW > maxW) paddleW = maxW;
                if (paddle) lv_obj_set_width(paddle, paddleW);
                // Re-clamp paddle position
                if (paddleX + paddleW > areaW) paddleX = (float)(areaW - paddleW);
                if (paddle) lv_obj_set_x(paddle, (int)paddleX);
            }
            break;

        case PowerUpType::ExtraLife:
            lives++;
            if (sfxEngine) sfxEngine->play(SfxId::OneUp);
            updateScoreDisplay();
            break;

        case PowerUpType::Slow:
            if (slowRecoveryTicks <= 0) {
                // First slow: save speed and apply reduction
                originalBallSpeed = ballSpeed;
                ballSpeed *= 0.6f;
                // Scale all active ball velocities
                for (int b = 0; b < MAX_BALLS; b++) {
                    if (!balls[b].active) continue;
                    float curSpd = std::sqrt(balls[b].vx * balls[b].vx + balls[b].vy * balls[b].vy);
                    if (curSpd > 0.01f) {
                        float scale = ballSpeed / curSpd;
                        balls[b].vx *= scale;
                        balls[b].vy *= scale;
                    }
                }
            }
            // Reset (or extend) recovery timer
            slowRecoveryTicks = SLOW_RECOVERY_TICKS;
            break;

        case PowerUpType::Catch:
            catchActive = true;
            catchBallIndex = -1;
            break;

        case PowerUpType::Split:
            splitBalls();
            break;

        case PowerUpType::Laser:
            laserActive = true;
            laserCooldown = 0;
            break;

        case PowerUpType::BreakOut:
            openExit();
            break;
    }
}

void Breakout::clearPowerUps() {
    // Reset extend
    if (extendActive) {
        extendActive = false;
        paddleW = originalPaddleW;
        if (paddle) lv_obj_set_width(paddle, paddleW);
    }

    // Reset catch
    catchActive = false;
    catchBallIndex = -1;
    catchAutoReleaseTicks = 0;

    // Reset slow
    if (slowRecoveryTicks > 0) {
        ballSpeed = baseBallSpeed + (level - 1) * 0.3f;
        slowRecoveryTicks = 0;
    }

    // Reset laser
    laserActive = false;
    laserCooldown = 0;
    for (int i = 0; i < MAX_LASERS; i++) {
        lasers[i].active = false;
        if (lasers[i].obj) lv_obj_add_flag(lasers[i].obj, LV_OBJ_FLAG_HIDDEN);
    }

    // Clear capsules
    for (int i = 0; i < MAX_CAPSULES; i++) {
        capsules[i].active = false;
        if (capsuleObjs[i]) lv_obj_add_flag(capsuleObjs[i], LV_OBJ_FLAG_HIDDEN);
    }

    // Close exit
    closeExit();
}

// ── Multi-Ball ───────────────────────────────────────────────

void Breakout::splitBalls() {
    // Find first active ball to split from
    int sourceIdx = -1;
    for (int i = 0; i < MAX_BALLS; i++) {
        if (balls[i].active) { sourceIdx = i; break; }
    }
    if (sourceIdx < 0) return;

    BallState& src = balls[sourceIdx];
    int spawned = 0;
    for (int i = 0; i < MAX_BALLS && spawned < 2; i++) {
        if (balls[i].active) continue;
        balls[i].active = true;
        balls[i].x = src.x;
        balls[i].y = src.y;

        // Diverging angles: +30 and -30 degrees from source
        float angle = (spawned == 0) ? 0.5f : -0.5f;
        float speed = std::sqrt(src.vx * src.vx + src.vy * src.vy);
        if (speed < 0.01f) speed = ballSpeed;
        float srcAngle = std::atan2(src.vy, src.vx);
        balls[i].vx = speed * std::cos(srcAngle + angle);
        balls[i].vy = speed * std::sin(srcAngle + angle);

        if (balls[i].obj) {
            lv_obj_set_pos(balls[i].obj, (int)balls[i].x, (int)balls[i].y);
            lv_obj_clear_flag(balls[i].obj, LV_OBJ_FLAG_HIDDEN);
        }
        spawned++;
    }
    activeBallCount += spawned;
}

void Breakout::updateBalls() {
    for (int b = 0; b < MAX_BALLS; b++) {
        if (!balls[b].active) continue;

        // Caught ball follows paddle
        if (catchActive && catchBallIndex == b) {
            balls[b].x = paddleX + catchOffsetX;
            balls[b].y = (float)(paddleYPos - ballSize - 2);
            if (balls[b].obj) lv_obj_set_pos(balls[b].obj, (int)balls[b].x, (int)balls[b].y);

            catchAutoReleaseTicks++;
            if (catchAutoReleaseTicks >= CATCH_AUTO_RELEASE_TICKS) {
                // Auto-release
                balls[b].vx = (esp_random() % 2 ? 1.0f : -1.0f) * ballSpeed * 0.7f;
                balls[b].vy = -ballSpeed;
                catchBallIndex = -1;
                catchAutoReleaseTicks = 0;
            }
            continue;
        }

        // Move ball
        balls[b].x += balls[b].vx;
        balls[b].y += balls[b].vy;

        // Left wall collision
        if (balls[b].x < 0) {
            balls[b].x = 0;
            balls[b].vx = -balls[b].vx;
            if (sfxEngine) sfxEngine->play(SfxId::Click);
        }

        // Right wall collision (ball always bounces, exit is paddle-only)
        if (balls[b].x + ballSize > areaW) {
            balls[b].x = (float)(areaW - ballSize);
            balls[b].vx = -balls[b].vx;
            if (sfxEngine) sfxEngine->play(SfxId::Click);
        }

        // Top wall
        if (balls[b].y < 0) {
            balls[b].y = 0;
            balls[b].vy = -balls[b].vy;
            if (sfxEngine) sfxEngine->play(SfxId::Click);
        }

        // Bottom edge
        if (balls[b].y + ballSize > areaH) {
            balls[b].active = false;
            if (balls[b].obj) lv_obj_add_flag(balls[b].obj, LV_OBJ_FLAG_HIDDEN);
            activeBallCount--;
            if (activeBallCount <= 0) {
                activeBallCount = 0;
                loseLife();
                return;
            }
            continue;
        }

        // Paddle collision
        if (balls[b].vy > 0 &&
            balls[b].x + ballSize > paddleX && balls[b].x < paddleX + paddleW &&
            balls[b].y + ballSize > paddleYPos && balls[b].y < paddleYPos + paddleH) {

            if (catchActive && catchBallIndex < 0) {
                // Catch the ball
                catchBallIndex = b;
                catchOffsetX = balls[b].x - paddleX;
                catchAutoReleaseTicks = 0;
                balls[b].vx = 0;
                balls[b].vy = 0;
                balls[b].y = (float)(paddleYPos - ballSize - 2);
                if (sfxEngine) sfxEngine->play(SfxId::Click);
            } else {
                // Normal bounce
                float hitPos = (balls[b].x + ballSize / 2.0f - paddleX) / (float)paddleW;
                float angle = (hitPos - 0.5f) * 2.0f;
                balls[b].vx = angle * ballSpeed;
                balls[b].vy = -std::fabs(balls[b].vy);
                if (std::fabs(balls[b].vy) < ballSpeed * 0.3f) {
                    balls[b].vy = -ballSpeed * 0.3f;
                }
                balls[b].y = (float)(paddleYPos - ballSize);
                if (sfxEngine) sfxEngine->play(SfxId::Click);
            }
        }

        // Brick collisions for this ball
        for (int r = 0; r < rows; r++) {
            for (int c = 0; c < cols; c++) {
                int idx = r * cols + c;
                if (!brickAlive[idx]) continue;

                float bx = (float)(brickOffsetX + c * (brickW + brickGap));
                float by = (float)(brickOffsetY + r * (brickH + brickGap));

                if (balls[b].x + ballSize > bx && balls[b].x < bx + brickW &&
                    balls[b].y + ballSize > by && balls[b].y < by + brickH) {

                    hitBrick(idx);

                    // Bounce direction
                    float overlapLeft = balls[b].x + ballSize - bx;
                    float overlapRight = bx + brickW - balls[b].x;
                    float overlapTop = balls[b].y + ballSize - by;
                    float overlapBottom = by + brickH - balls[b].y;
                    float minOverlapX = overlapLeft < overlapRight ? overlapLeft : overlapRight;
                    float minOverlapY = overlapTop < overlapBottom ? overlapTop : overlapBottom;

                    if (minOverlapX < minOverlapY) {
                        balls[b].vx = -balls[b].vx;
                    } else {
                        balls[b].vy = -balls[b].vy;
                    }

                    goto nextBall; // One brick per ball per tick
                }
            }
        }

        nextBall:
        if (balls[b].obj && balls[b].active) {
            lv_obj_set_pos(balls[b].obj, (int)balls[b].x, (int)balls[b].y);
        }
    }
}

// ── Laser System ─────────────────────────────────────────────

void Breakout::fireLaser() {
    for (int i = 0; i < MAX_LASERS; i++) {
        if (!lasers[i].active) {
            lasers[i].active = true;
            lasers[i].x = paddleX + paddleW / 2.0f - laserW / 2.0f;
            lasers[i].y = (float)(paddleYPos - laserH);
            if (lasers[i].obj) {
                lv_obj_set_pos(lasers[i].obj, (int)lasers[i].x, (int)lasers[i].y);
                lv_obj_clear_flag(lasers[i].obj, LV_OBJ_FLAG_HIDDEN);
            }
            if (sfxEngine) sfxEngine->play(SfxId::Laser);
            return;
        }
    }
}

void Breakout::updateLasers() {
    if (!laserActive) return;

    // Auto-fire
    laserCooldown--;
    if (laserCooldown <= 0) {
        fireLaser();
        laserCooldown = LASER_COOLDOWN_TICKS;
    }

    for (int i = 0; i < MAX_LASERS; i++) {
        if (!lasers[i].active) continue;

        lasers[i].y -= laserSpeed;

        if (lasers[i].y + laserH < 0) {
            lasers[i].active = false;
            if (lasers[i].obj) lv_obj_add_flag(lasers[i].obj, LV_OBJ_FLAG_HIDDEN);
            continue;
        }

        if (lasers[i].obj && lasers[i].active) {
            lv_obj_set_pos(lasers[i].obj, (int)lasers[i].x, (int)lasers[i].y);
        }
    }

    // Check all laser-brick collisions once per tick
    checkLaserBrickCollisions();
}

void Breakout::checkLaserBrickCollisions() {
    for (int li = 0; li < MAX_LASERS; li++) {
        if (!lasers[li].active) continue;

        for (int r = 0; r < rows; r++) {
            for (int c = 0; c < cols; c++) {
                int idx = r * cols + c;
                if (!brickAlive[idx]) continue;

                float bx = (float)(brickOffsetX + c * (brickW + brickGap));
                float by = (float)(brickOffsetY + r * (brickH + brickGap));

                if (lasers[li].x + laserW > bx && lasers[li].x < bx + brickW &&
                    lasers[li].y + laserH > by && lasers[li].y < by + brickH) {
                    hitBrick(idx);
                    lasers[li].active = false;
                    if (lasers[li].obj) lv_obj_add_flag(lasers[li].obj, LV_OBJ_FLAG_HIDDEN);
                    goto nextLaser;
                }
            }
        }
        nextLaser:;
    }
}

// ── BreakOut Exit ────────────────────────────────────────────

void Breakout::openExit() {
    exitOpen = true;
    if (exitIndicator) lv_obj_clear_flag(exitIndicator, LV_OBJ_FLAG_HIDDEN);
}

void Breakout::closeExit() {
    exitOpen = false;
    if (exitIndicator) lv_obj_add_flag(exitIndicator, LV_OBJ_FLAG_HIDDEN);
}

// ── Main Game Tick ───────────────────────────────────────────

void Breakout::update() {
    if (state == GameState::Ready) {
        // Ball follows paddle
        balls[0].x = paddleX + paddleW / 2.0f - ballSize / 2.0f;
        if (balls[0].obj) lv_obj_set_x(balls[0].obj, (int)balls[0].x);
        return;
    }
    if (state != GameState::Playing) return;

    // Slow ball recovery
    if (slowRecoveryTicks > 0) {
        slowRecoveryTicks--;
        if (slowRecoveryTicks <= 0) {
            // Restore normal speed
            float targetSpeed = baseBallSpeed + (level - 1) * 0.3f;
            ballSpeed = targetSpeed;
        } else {
            // Gradually recover speed
            float targetSpeed = baseBallSpeed + (level - 1) * 0.3f;
            float progress = 1.0f - (float)slowRecoveryTicks / SLOW_RECOVERY_TICKS;
            ballSpeed = originalBallSpeed * 0.6f + (targetSpeed - originalBallSpeed * 0.6f) * progress;
        }
    }

    // Update all balls (movement, collisions)
    updateBalls();

    // Check if paddle reaches BreakOut exit
    if (exitOpen && paddleX + paddleW >= areaW - 8) {
        score += 10000;
        updateScoreDisplay();
        saveHighScore(score);
        if (sfxEngine) sfxEngine->play(SfxId::Warp);
        winLevel();
        return;
    }

    // Update capsules
    updateCapsules();

    // Update lasers
    updateLasers();
}

// ── Display Updates ──────────────────────────────────────────

void Breakout::updateScoreDisplay() {
    if (scoreLabel) {
        if (level > 1) {
            lv_label_set_text_fmt(scoreLabel, "L%d: %d", level, score);
        } else {
            lv_label_set_text_fmt(scoreLabel, "SCORE: %d", score);
        }
    }
    if (livesLabel) {
        lv_label_set_text_fmt(livesLabel, "L: %d", lives);
    }
}

void Breakout::updateMessage() {
    if (!messageLabel) return;

    switch (state) {
        case GameState::Ready: {
            char buf[64];
            const char* input_hint = "Touch";
            if (tt_lvgl_hardware_keyboard_is_available()) {
                input_hint = "Space";
            }
            if (level > 1) {
                snprintf(buf, sizeof(buf), "Level %d\n%s to start!", level, input_hint);
            } else if (highScore > 0) {
                snprintf(buf, sizeof(buf), "%s to start!\nBest Score: %d", input_hint, (int)highScore);
            } else {
                snprintf(buf, sizeof(buf), "%s to start!", input_hint);
            }
            lv_label_set_text(messageLabel, buf);
            lv_obj_clear_flag(messageLabel, LV_OBJ_FLAG_HIDDEN);
            break;
        }
        case GameState::Playing:
            lv_obj_add_flag(messageLabel, LV_OBJ_FLAG_HIDDEN);
            break;
        case GameState::Paused:
            lv_label_set_text(messageLabel, "PAUSED");
            lv_obj_clear_flag(messageLabel, LV_OBJ_FLAG_HIDDEN);
            break;
        case GameState::GameOver: {
            char buf[64];
            if (score > highScore && score > 0) {
                snprintf(buf, sizeof(buf), "NEW HIGH SCORE!\n%d", score);
            } else {
                snprintf(buf, sizeof(buf), "Game Over\nScore: %d\nBest Score: %d", score, (int)highScore);
            }
            lv_label_set_text(messageLabel, buf);
            lv_obj_clear_flag(messageLabel, LV_OBJ_FLAG_HIDDEN);
            break;
        }
    }
    lv_obj_center(messageLabel);
}

void Breakout::updateSoundIcon() {
    if (soundBtnIcon) {
        lv_label_set_text(soundBtnIcon, soundEnabled ? LV_SYMBOL_VOLUME_MAX : LV_SYMBOL_MUTE);
    }
}

// ── Event Callbacks ──────────────────────────────────────────

void Breakout::onTick(lv_timer_t* timer) {
    Breakout* self = static_cast<Breakout*>(lv_timer_get_user_data(timer));
    if (self) self->update();
}

void Breakout::onPressed(lv_event_t* e) {
    Breakout* self = static_cast<Breakout*>(lv_event_get_user_data(e));
    if (!self || !self->paddle) return;
    if (self->state == GameState::GameOver || self->state == GameState::Paused) return;

    lv_point_t point;
    lv_indev_get_point(lv_indev_active(), &point);

    // Move paddle to touch X (centered on finger)
    self->paddleX = (float)(point.x - self->paddleW / 2);

    // Clamp to game area bounds
    if (self->paddleX < 0) self->paddleX = 0;
    if (self->paddleX + self->paddleW > self->areaW)
        self->paddleX = (float)(self->areaW - self->paddleW);

    lv_obj_set_x(self->paddle, (int)self->paddleX);

    // In ready state, ball follows paddle
    if (self->state == GameState::Ready) {
        self->balls[0].x = self->paddleX + self->paddleW / 2.0f - self->ballSize / 2.0f;
        if (self->balls[0].obj) lv_obj_set_x(self->balls[0].obj, (int)self->balls[0].x);
    }
}

void Breakout::onClicked(lv_event_t* e) {
    Breakout* self = static_cast<Breakout*>(lv_event_get_user_data(e));
    if (!self) return;

    if (self->state == GameState::Ready) {
        self->launchBall();
    } else if (self->state == GameState::GameOver) {
        self->startGame();
    } else if (self->state == GameState::Paused) {
        self->togglePause();
    } else if (self->state == GameState::Playing && self->catchActive && self->catchBallIndex >= 0) {
        self->launchBall();
    }
}

void Breakout::onKey(lv_event_t* e) {
    Breakout* self = static_cast<Breakout*>(lv_event_get_user_data(e));
    if (!self) return;

    uint32_t key = lv_event_get_key(e);

    switch (key) {
        case LV_KEY_LEFT:
        case 'a':
        case 'A':
        case ',':
            if (self->state == GameState::GameOver || self->state == GameState::Paused) break;
            self->paddleX -= self->paddleSpeed;
            if (self->paddleX < 0) self->paddleX = 0;
            if (self->paddle) lv_obj_set_x(self->paddle, (int)self->paddleX);
            if (self->state == GameState::Ready) self->resetBall();
            break;

        case LV_KEY_RIGHT:
        case 'd':
        case 'D':
        case '/':
            if (self->state == GameState::GameOver || self->state == GameState::Paused) break;
            self->paddleX += self->paddleSpeed;
            if (self->paddleX + self->paddleW > self->areaW)
                self->paddleX = (float)(self->areaW - self->paddleW);
            if (self->paddle) lv_obj_set_x(self->paddle, (int)self->paddleX);
            if (self->state == GameState::Ready) self->resetBall();
            break;

        case LV_KEY_ENTER:
        case ' ':
            if (self->state == GameState::Ready) {
                self->launchBall();
            } else if (self->state == GameState::GameOver) {
                self->startGame();
            } else if (self->state == GameState::Paused) {
                self->togglePause();
            } else if (self->state == GameState::Playing) {
                if (self->catchActive && self->catchBallIndex >= 0) {
                    self->launchBall();
                } else {
                    self->togglePause();
                }
            }
            break;
        case LV_KEY_ESC:
        case 'q':
        case 'Q': {
            // Exit key/edit mode - remove from group so navigation is restored
            // Re-entry: tap/click the game area to return to key mode
            lv_group_t* group = lv_group_get_default();
            if (group) lv_group_set_editing(group, false);
            lv_group_remove_obj(lv_event_get_current_target_obj(e));
            break;
        }
    }
}

void Breakout::onPauseClicked(lv_event_t* e) {
    Breakout* self = static_cast<Breakout*>(lv_event_get_user_data(e));
    if (self) self->togglePause();
}

void Breakout::onSoundToggled(lv_event_t* e) {
    Breakout* self = static_cast<Breakout*>(lv_event_get_user_data(e));
    if (!self) return;

    soundEnabled = !soundEnabled;
    if (self->sfxEngine) self->sfxEngine->setEnabled(soundEnabled);
    saveSoundSetting(soundEnabled);
    self->updateSoundIcon();
}

void Breakout::onReenterKeyMode(lv_event_t* e) {
    lv_obj_t* area = lv_event_get_current_target_obj(e);
    lv_group_t* group = lv_group_get_default();
    if (!group) return;
    if (lv_obj_get_group(area) == NULL) {
        lv_group_add_obj(group, area);
    }
    lv_group_focus_obj(area);
    lv_group_set_editing(group, true);
}
