#include "WidgetManager.hpp"
#include "widgets/WidgetFactory.hpp"
#include "SdCard.hpp"
#include "esp_lv_adapter.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "cJSON.h"
#include <memory>
#include <new>
#include <cstdio>
#include <cstdint>
#include <cstring>
#include <cstdlib>

static const char* TAG = "WidgetMgr";
static constexpr uint8_t SCREEN_ID_NONE = 0xFF;

static bool is_valid_utf8(const char* text, size_t len) {
    size_t i = 0;
    while (i < len) {
        uint8_t c = static_cast<uint8_t>(text[i]);
        if (c < 0x80) {
            i++;
            continue;
        }
        if ((c & 0xE0) == 0xC0) {
            if (i + 1 >= len) return false;
            uint8_t c1 = static_cast<uint8_t>(text[i + 1]);
            if ((c1 & 0xC0) != 0x80) return false;
            i += 2;
            continue;
        }
        if ((c & 0xF0) == 0xE0) {
            if (i + 2 >= len) return false;
            uint8_t c1 = static_cast<uint8_t>(text[i + 1]);
            uint8_t c2 = static_cast<uint8_t>(text[i + 2]);
            if ((c1 & 0xC0) != 0x80 || (c2 & 0xC0) != 0x80) return false;
            i += 3;
            continue;
        }
        if ((c & 0xF8) == 0xF0) {
            if (i + 3 >= len) return false;
            uint8_t c1 = static_cast<uint8_t>(text[i + 1]);
            uint8_t c2 = static_cast<uint8_t>(text[i + 2]);
            uint8_t c3 = static_cast<uint8_t>(text[i + 3]);
            if ((c1 & 0xC0) != 0x80 || (c2 & 0xC0) != 0x80 || (c3 & 0xC0) != 0x80) return false;
            i += 4;
            continue;
        }
        return false;
    }
    return true;
}

static void latin1_to_utf8(const char* src, size_t src_len, char* dst, size_t dst_size) {
    if (dst_size == 0) return;
    size_t di = 0;
    for (size_t si = 0; si < src_len; ++si) {
        uint8_t c = static_cast<uint8_t>(src[si]);
        if (c < 0x80) {
            if (di + 1 >= dst_size) break;
            dst[di++] = static_cast<char>(c);
        } else {
            if (di + 2 >= dst_size) break;
            dst[di++] = static_cast<char>(0xC0 | (c >> 6));
            dst[di++] = static_cast<char>(0x80 | (c & 0x3F));
        }
    }
    dst[di] = '\0';
}

static WidgetConfig makeButtonLabelChild(const WidgetConfig& button) {
    WidgetConfig label = WidgetConfig::createLabel(0, 0, 0, button.text);
    label.parentId = button.id;
    if (button.width > 0) label.width = button.width;
    if (button.height > 0) label.height = button.height;
    label.fontSize = button.fontSize;
    label.textAlign = button.textAlign;
    label.textColor = button.textColor;
    label.textSource = TextSource::STATIC;
    label.bgOpacity = 0;
    label.borderRadius = 0;
    label.shadow.enabled = false;
    // Preserve existing icon config if any
    label.iconCodepoint = button.iconCodepoint;
    label.iconPosition = button.iconPosition;
    label.iconSize = button.iconSize;
    label.iconGap = button.iconGap;
    if (label.text[0] == '\0') {
        strncpy(label.text, "Button", MAX_TEXT_LEN - 1);
        label.text[MAX_TEXT_LEN - 1] = '\0';
    }
    return label;
}

static void ensureButtonLabels(ScreenConfig& screen) {
    bool hasLabelChild[MAX_WIDGETS] = {};
    for (uint8_t i = 0; i < screen.widgetCount; i++) {
        const WidgetConfig& w = screen.widgets[i];
        if (w.type == WidgetType::LABEL && w.parentId >= 0 && w.parentId < MAX_WIDGETS) {
            hasLabelChild[w.parentId] = true;
        }
    }

    const uint8_t initialCount = screen.widgetCount;
    for (uint8_t i = 0; i < initialCount; i++) {
        WidgetConfig& w = screen.widgets[i];
        if (w.type != WidgetType::BUTTON) continue;

        w.isContainer = true;

        if (w.id < MAX_WIDGETS && hasLabelChild[w.id]) continue;

        WidgetConfig label = makeButtonLabelChild(w);
        int newId = screen.addWidget(label);
        if (newId < 0) {
            ESP_LOGW(TAG, "No space to add label child for button %d", w.id);
            w.isContainer = false;
            continue;
        }
        if (w.id < MAX_WIDGETS) {
            hasLabelChild[w.id] = true;
        }
    }
}

// WidgetManager implementation
WidgetManager& WidgetManager::instance() {
    static WidgetManager inst;
    return inst;
}

WidgetManager::WidgetManager() {
    // widgets_ is default-initialized to nullptr
    uiQueue_ = xQueueCreate(UI_EVENT_QUEUE_LEN, sizeof(UiEvent));
    if (!uiQueue_) {
        ESP_LOGE(TAG, "Failed to create UI event queue");
    }
    createDefaultConfig();
    activeScreenId_ = config_.startScreenId;
    lastActivityUs_ = esp_timer_get_time();
}

void WidgetManager::createDefaultConfig() {
    config_.clear();
    config_.screenCount = 1;
    ScreenConfig& screen = config_.screens[0];
    screen.clear(0, "Screen 1");

    // Default: Temperature label
    auto tempLabel = WidgetConfig::createKnxLabel(0, 50, 20,
        TextSource::KNX_DPT_TEMP, 1, "%.1f °C");
    tempLabel.fontSize = 3; // 28pt
    screen.addWidget(tempLabel);

    // Default: KNX Prog button
    auto progBtn = WidgetConfig::createButton(1, 50, 100, "KNX Prog", 0, true);
    progBtn.bgColor = {200, 50, 50}; // Red
    screen.addWidget(progBtn);

    ensureButtonLabels(screen);

    config_.startScreenId = screen.id;
    config_.standbyEnabled = false;
    config_.standbyScreenId = 0xFF;
    config_.standbyMinutes = 0;
    activeScreenId_ = screen.id;
}

void WidgetManager::init() {
    loadFromSdCard();
    if (config_.findScreen(config_.startScreenId)) {
        activeScreenId_ = config_.startScreenId;
    } else if (config_.screenCount > 0) {
        activeScreenId_ = config_.screens[0].id;
    } else {
        activeScreenId_ = 0;
    }
    lastActivityUs_ = esp_timer_get_time();
    ESP_LOGI(TAG, "WidgetManager initialized with %d screens", config_.screenCount);
}

void WidgetManager::loadFromSdCard() {
    if (!SdCard::instance().isMounted()) {
        ESP_LOGI(TAG, "SD card not mounted, using defaults");
        return;
    }

    FILE* f = fopen(CONFIG_FILE, "r");
    if (!f) {
        ESP_LOGI(TAG, "No config file found, using defaults");
        return;
    }

    fseek(f, 0, SEEK_END);
    long size = ftell(f);
    fseek(f, 0, SEEK_SET);

    if (size <= 0 || size > 32768) {
        ESP_LOGE(TAG, "Invalid config file size: %ld", size);
        fclose(f);
        return;
    }

    char* json = new char[size + 1];
    if (!json) {
        ESP_LOGE(TAG, "Failed to allocate memory for config");
        fclose(f);
        return;
    }

    size_t read = fread(json, 1, size, f);
    fclose(f);
    json[read] = '\0';

    bool success = updateConfigFromJson(json);
    delete[] json;

    if (success) {
        ESP_LOGI(TAG, "Loaded %d screens from SD card", config_.screenCount);
    } else {
        ESP_LOGE(TAG, "Failed to parse config file");
    }
}

void WidgetManager::saveToSdCard() {
    if (!SdCard::instance().isMounted()) {
        ESP_LOGE(TAG, "SD card not mounted, cannot save config");
        return;
    }

    char* json = new char[32768];
    if (!json) {
        ESP_LOGE(TAG, "Failed to allocate memory for JSON");
        return;
    }

    getConfigJson(json, 32768);

    FILE* f = fopen(CONFIG_FILE, "w");
    if (!f) {
        ESP_LOGE(TAG, "Failed to open config file for writing");
        delete[] json;
        return;
    }

    size_t written = fwrite(json, 1, strlen(json), f);
    fclose(f);
    delete[] json;

    if (written > 0) {
        ESP_LOGI(TAG, "Saved %d screens to SD card", config_.screenCount);
    } else {
        ESP_LOGE(TAG, "Failed to write config file");
    }
}

void WidgetManager::applyConfig() {
    if (!config_.findScreen(activeScreenId_)) {
        if (config_.findScreen(config_.startScreenId)) {
            activeScreenId_ = config_.startScreenId;
        } else if (config_.screenCount > 0) {
            activeScreenId_ = config_.screens[0].id;
        }
    }
    applyScreen(activeScreenId_);
}

void WidgetManager::saveAndApply() {
    saveToSdCard();
    applyConfig();
    ESP_LOGI(TAG, "Config saved and applied");
}

void WidgetManager::resetToDefaults() {
    closeModal();
    standbyActive_ = false;
    standbyWakePending_ = false;
    standbyReturnScreenId_ = SCREEN_ID_NONE;
    previousScreenId_ = SCREEN_ID_NONE;
    createDefaultConfig();
    saveAndApply();
    ESP_LOGI(TAG, "Reset to defaults");
}

ScreenConfig* WidgetManager::activeScreen() {
    if (modalContainer_ && modalScreenId_ != SCREEN_ID_NONE) {
        return config_.findScreen(modalScreenId_);
    }
    return config_.findScreen(activeScreenId_);
}

const ScreenConfig* WidgetManager::activeScreen() const {
    if (modalContainer_ && modalScreenId_ != SCREEN_ID_NONE) {
        return config_.findScreen(modalScreenId_);
    }
    return config_.findScreen(activeScreenId_);
}

const ScreenConfig* WidgetManager::currentScreen() const {
    return activeScreen();
}

void WidgetManager::applyScreen(uint8_t screenId) {
    ESP_LOGI(TAG, "applyScreen(%d) - start", screenId);

    ScreenConfig* screen = config_.findScreen(screenId);
    if (!screen) {
        ESP_LOGW(TAG, "Screen %d not found", screenId);
        return;
    }

    ensureButtonLabels(*screen);

    if (modalContainer_) {
        ESP_LOGI(TAG, "Closing modal first");
        closeModal();
    }

    ESP_LOGI(TAG, "Acquiring LVGL lock...");
    if (esp_lv_adapter_lock(-1) != ESP_OK) {
        ESP_LOGE(TAG, "Failed to acquire LVGL lock!");
        return;
    }
    ESP_LOGI(TAG, "LVGL lock acquired");

    // Reset all input devices BEFORE destroying widgets to clear any
    // pending input state and prevent use-after-free on widget objects
    ESP_LOGI(TAG, "Resetting input devices...");
    lv_indev_t* indev = lv_indev_get_next(nullptr);
    while (indev) {
        lv_indev_reset(indev, nullptr);
        indev = lv_indev_get_next(indev);
    }
    ESP_LOGI(TAG, "Input devices reset");

    // Now destroy C++ widgets (which deletes LVGL objects) under LVGL lock
    ESP_LOGI(TAG, "Destroying widgets...");
    destroyAllWidgets();
    ESP_LOGI(TAG, "Widgets destroyed");

    ESP_LOGI(TAG, "Creating new widgets for screen '%s' (%d widgets)...",
             screen->name, screen->widgetCount);
    lv_obj_t* root = lv_scr_act();
    createAllWidgets(*screen, root);
    ESP_LOGI(TAG, "Widgets created");

    esp_lv_adapter_unlock();
    ESP_LOGI(TAG, "applyScreen(%d) - complete", screenId);
}

void WidgetManager::showModalScreen(const ScreenConfig& screen) {
    if (modalContainer_) {
        closeModal();
    }

    if (esp_lv_adapter_lock(-1) != ESP_OK) return;

    // Reset all input devices BEFORE destroying widgets
    lv_indev_t* indev = lv_indev_get_next(nullptr);
    while (indev) {
        lv_indev_reset(indev, nullptr);
        indev = lv_indev_get_next(indev);
    }

    // Destroy any existing widgets before creating modal widgets
    destroyAllWidgets();

    lv_disp_t* disp = lv_disp_get_default();
    int32_t dispWidth = disp ? lv_disp_get_hor_res(disp) : 1280;
    int32_t dispHeight = disp ? lv_disp_get_ver_res(disp) : 800;

    // Create semi-transparent background overlay if dimming enabled
    lv_obj_t* dimmer = nullptr;
    if (screen.modalDimBackground) {
        dimmer = lv_obj_create(lv_layer_top());
        if (dimmer) {
            lv_obj_remove_style_all(dimmer);
            lv_obj_set_size(dimmer, dispWidth, dispHeight);
            lv_obj_set_style_bg_color(dimmer, lv_color_black(), 0);
            lv_obj_set_style_bg_opa(dimmer, LV_OPA_50, 0);
            lv_obj_clear_flag(dimmer, LV_OBJ_FLAG_SCROLLABLE);
        }
    }

    // Create modal container
    lv_obj_t* modal = lv_obj_create(lv_layer_top());
    lv_obj_clear_flag(modal, LV_OBJ_FLAG_SCROLLABLE);

    // Calculate modal size
    int32_t modalWidth = screen.modalWidth;
    int32_t modalHeight = screen.modalHeight;

    // Auto-size: calculate from widget bounds if not specified
    if (modalWidth <= 0 || modalHeight <= 0) {
        int32_t maxX = 0, maxY = 0;
        for (uint8_t i = 0; i < screen.widgetCount; i++) {
            const WidgetConfig& w = screen.widgets[i];
            if (w.visible) {
                int32_t right = w.x + w.width;
                int32_t bottom = w.y + w.height;
                if (right > maxX) maxX = right;
                if (bottom > maxY) maxY = bottom;
            }
        }
        if (modalWidth <= 0) modalWidth = maxX + 40;  // Add padding
        if (modalHeight <= 0) modalHeight = maxY + 40;
    }

    lv_obj_set_size(modal, modalWidth, modalHeight);

    // Position modal (0 = centered)
    if (screen.modalX == 0 && screen.modalY == 0) {
        lv_obj_center(modal);
    } else {
        lv_obj_set_pos(modal, screen.modalX, screen.modalY);
    }

    // Style modal
    lv_obj_set_style_bg_color(modal, lv_color_make(
        screen.backgroundColor.r,
        screen.backgroundColor.g,
        screen.backgroundColor.b), 0);
    lv_obj_set_style_bg_opa(modal, LV_OPA_COVER, 0);
    lv_obj_set_style_radius(modal, screen.modalBorderRadius, 0);
    lv_obj_set_style_border_width(modal, 0, 0);
    lv_obj_set_style_pad_all(modal, 0, 0);

    // Add shadow for modal
    lv_obj_set_style_shadow_color(modal, lv_color_black(), 0);
    lv_obj_set_style_shadow_opa(modal, LV_OPA_30, 0);
    lv_obj_set_style_shadow_width(modal, 20, 0);
    lv_obj_set_style_shadow_spread(modal, 5, 0);

    modalContainer_ = modal;
    modalDimmer_ = dimmer;
    modalScreenId_ = screen.id;

    // Create widgets inside modal (not on full screen)
    screen_ = modal;
    for (uint8_t i = 0; i < screen.widgetCount; i++) {
        const WidgetConfig& cfg = screen.widgets[i];
        auto widget = WidgetFactory::create(cfg);
        if (widget && cfg.id < MAX_WIDGETS) {
            widget->create(modal);
            widget->applyStyle();
            widgets_[cfg.id] = std::move(widget);
        }
    }

    esp_lv_adapter_unlock();
    ESP_LOGI(TAG, "Modal screen %d opened (%ldx%ld)", screen.id, modalWidth, modalHeight);
}

void WidgetManager::closeModal() {
    printf("WM: closeModal Start. Container=%p\n", (void*)modalContainer_);
    fflush(stdout);
    if (!modalContainer_) {
        return;
    }

    if (esp_lv_adapter_lock(-1) != ESP_OK) {
        ESP_LOGE(TAG, "closeModal: Failed to lock LVGL");
        return;
    }

    // Reset input devices
    lv_indev_t* indev = lv_indev_get_next(nullptr);
    while (indev) {
        lv_indev_reset(indev, nullptr);
        indev = lv_indev_get_next(indev);
    }

    // Destroy widgets first
    printf("WM: closeModal destroying widgets...\n");
    fflush(stdout);
    destroyAllWidgets();

    if (modalDimmer_) {
        if (lv_obj_is_valid(modalDimmer_)) lv_obj_delete(modalDimmer_);
        modalDimmer_ = nullptr;
    }
    
    if (lv_obj_is_valid(modalContainer_)) {
        lv_obj_delete(modalContainer_);
    } else {
        printf("WM: Warning: modalContainer_ was invalid!\n");
        fflush(stdout);
    }
    modalContainer_ = nullptr;
    modalScreenId_ = SCREEN_ID_NONE;
    
    esp_lv_adapter_unlock();
    printf("WM: closeModal Complete\n");
    fflush(stdout);
}

void WidgetManager::showScreen(uint8_t screenId) {
    ESP_LOGI(TAG, "showScreen(%d) called", screenId);

    ScreenConfig* screen = config_.findScreen(screenId);
    if (!screen) {
        ESP_LOGW(TAG, "Screen %d not found", screenId);
        return;
    }

    ESP_LOGI(TAG, "Found screen '%s', mode=%d", screen->name,
             static_cast<int>(screen->mode));

    if (screen->mode == ScreenMode::MODAL) {
        showModalScreen(*screen);
        return;
    }

    previousScreenId_ = activeScreenId_;
    activeScreenId_ = screen->id;
    standbyActive_ = false;
    applyScreen(activeScreenId_);
}

void WidgetManager::handleButtonAction(const WidgetConfig& cfg, lv_obj_t* target) {
    printf("WM: handleButtonAction btn=%d act=%d type=%d\n", cfg.id, (int)cfg.action, (int)cfg.type);
    fflush(stdout);

    if (cfg.type != WidgetType::BUTTON) {
        printf("WM: Not a button!\n");
        fflush(stdout);
        return;
    }

    onUserActivity();

    switch (cfg.action) {
        case ButtonAction::JUMP:
            printf("WM: Action JUMP to %d\n", cfg.targetScreen);
            fflush(stdout);
            navAction_ = ButtonAction::JUMP;
            navTargetScreen_ = cfg.targetScreen;
            navPending_ = true;
            navRequestUs_ = esp_timer_get_time();
            break;
        case ButtonAction::BACK:
            printf("WM: Action BACK\n");
            fflush(stdout);
            navAction_ = ButtonAction::BACK;
            navTargetScreen_ = SCREEN_ID_NONE;
            navPending_ = true;
            navRequestUs_ = esp_timer_get_time();
            break;
        case ButtonAction::KNX:
        default: {
            if (cfg.knxAddressWrite > 0) {
                bool state = false;
                if (target) {
                    state = (lv_obj_get_state(target) & LV_STATE_CHECKED) != 0;
                }
                ESP_LOGI(TAG, "Button %d clicked, KNX write to %d, state=%d",
                         cfg.id, cfg.knxAddressWrite, state);
                // TODO: Send KNX telegram
            }
            break;
        }
    }
}

void WidgetManager::goBack() {
    printf("WM: goBack called. Modal=%p Active=%d Prev=%d\n", 
           (void*)modalContainer_, activeScreenId_, previousScreenId_);
    fflush(stdout);

    if (modalContainer_) {
        printf("WM: Closing modal...\n");
        fflush(stdout);
        closeModal();
        printf("WM: Modal closed. Restoring screen %d\n", activeScreenId_);
        fflush(stdout);
        // Restore the active screen (which was in background)
        if (config_.findScreen(activeScreenId_)) {
            applyScreen(activeScreenId_);
        } else {
            ESP_LOGE(TAG, "Active screen %d not found after closing modal!", activeScreenId_);
            if (config_.findScreen(config_.startScreenId)) {
                activeScreenId_ = config_.startScreenId;
                applyScreen(activeScreenId_);
            }
        }
        return;
    }

    if (previousScreenId_ != SCREEN_ID_NONE && previousScreenId_ != activeScreenId_) {
        printf("WM: Going back to screen %d\n", previousScreenId_);
        fflush(stdout);
        if (config_.findScreen(previousScreenId_)) {
            activeScreenId_ = previousScreenId_;
            previousScreenId_ = SCREEN_ID_NONE;
            applyScreen(activeScreenId_);
        } else {
             ESP_LOGW(TAG, "Previous screen %d not found", previousScreenId_);
             previousScreenId_ = SCREEN_ID_NONE;
        }
    } else {
        printf("WM: No previous screen to go back to\n");
        fflush(stdout);
    }
}

void WidgetManager::enterStandby() {
    if (!config_.standbyEnabled || config_.standbyMinutes == 0) return;
    if (standbyActive_) return;
    if (config_.standbyScreenId == SCREEN_ID_NONE) return;

    ScreenConfig* standbyScreen = config_.findScreen(config_.standbyScreenId);
    if (!standbyScreen) return;

    standbyReturnScreenId_ = activeScreenId_;
    standbyActive_ = true;
    activeScreenId_ = standbyScreen->id;
    applyScreen(activeScreenId_);
}

void WidgetManager::loop() {
    bool didUiNav = false;
    if (navPending_) {
        int64_t now = esp_timer_get_time();
        // Increased delay to ensure touch events are fully processed
        if (now - navRequestUs_ >= NAV_DELAY_US) {
            navPending_ = false;
            ESP_LOGI(TAG, "Executing navigation: action=%d target=%d",
                     static_cast<int>(navAction_), navTargetScreen_);
            if (navAction_ == ButtonAction::JUMP) {
                showScreen(navTargetScreen_);
            } else if (navAction_ == ButtonAction::BACK) {
                goBack();
            }
            didUiNav = true;
            ESP_LOGI(TAG, "Navigation complete");
        }
    }

    if (standbyWakePending_) {
        standbyWakePending_ = false;
        if (standbyWakeTarget_ != SCREEN_ID_NONE) {
            activeScreenId_ = standbyWakeTarget_;
            applyScreen(activeScreenId_);
        }
        didUiNav = true;
    }

    processUiQueue();

    if (didUiNav) return;

    if (!config_.standbyEnabled || config_.standbyMinutes == 0) return;
    if (standbyActive_) return;
    if (config_.standbyScreenId == SCREEN_ID_NONE) return;

    int64_t now = esp_timer_get_time();
    int64_t idleUs = now - lastActivityUs_;
    int64_t timeoutUs = static_cast<int64_t>(config_.standbyMinutes) * 60 * 1000000LL;
    if (idleUs >= timeoutUs) {
        enterStandby();
    }
}

void WidgetManager::onUserActivity() {
    lastActivityUs_ = esp_timer_get_time();
    if (standbyActive_) {
        standbyActive_ = false;
        uint8_t returnId = standbyReturnScreenId_;
        if (returnId == SCREEN_ID_NONE) {
            returnId = config_.startScreenId;
        }
        standbyWakeTarget_ = returnId;
        standbyWakePending_ = true;
    }
}

void WidgetManager::destroyAllWidgets() {
    ESP_LOGI(TAG, "destroyAllWidgets: Start (%d widgets)", widgets_.size());
    // Destroy in reverse order (last created first)
    for (int i = static_cast<int>(widgets_.size()) - 1; i >= 0; i--) {
        if (widgets_[i]) {
            ESP_LOGD(TAG, "Destroying widget %d", i);
            widgets_[i].reset();
        }
    }
    ESP_LOGI(TAG, "destroyAllWidgets: Complete");
}

void WidgetManager::createAllWidgets(const ScreenConfig& screen, lv_obj_t* parent) {
    screen_ = parent;

    lv_obj_set_style_bg_color(parent, lv_color_make(
        screen.backgroundColor.r,
        screen.backgroundColor.g,
        screen.backgroundColor.b), 0);
    lv_obj_set_style_bg_opa(parent, LV_OPA_COVER, 0);

    // Pass 1: Create root widgets (parentId == -1)
    for (uint8_t i = 0; i < screen.widgetCount; i++) {
        const WidgetConfig& cfg = screen.widgets[i];
        if (cfg.parentId != -1) continue;

        auto widget = WidgetFactory::create(cfg);
        if (widget && cfg.id < MAX_WIDGETS) {
            widget->create(parent);
            widget->applyStyle();
            widgets_[cfg.id] = std::move(widget);
        }
    }

    // Pass 2: Create child widgets
    // Simple 1-level depth support for now. For deeper nesting, we'd need a topological sort or multiple passes.
    bool madeProgress = true;
    int remainingPasses = 10; // Prevent infinite loops
    
    while (madeProgress && remainingPasses > 0) {
        madeProgress = false;
        remainingPasses--;
        
        for (uint8_t i = 0; i < screen.widgetCount; i++) {
            const WidgetConfig& cfg = screen.widgets[i];
            
            // Skip if already created
            if (widgets_[cfg.id]) continue;
            
            // Skip if it's a root widget (should be created in Pass 1, but if failed/skipped, ignore)
            if (cfg.parentId == -1) continue;
            
            // Check if parent exists
            if (cfg.parentId >= 0 && cfg.parentId < MAX_WIDGETS && widgets_[cfg.parentId]) {
                // Parent exists! Get its LVGL object
                lv_obj_t* parentObj = widgets_[cfg.parentId]->getObj();
                if (parentObj) {
                    auto widget = WidgetFactory::create(cfg);
                    if (widget && cfg.id < MAX_WIDGETS) {
                        widget->create(parentObj);
                        widget->applyStyle();
                        widgets_[cfg.id] = std::move(widget);
                        madeProgress = true;
                    }
                }
            }
        }
    }
}

void WidgetManager::onKnxValue(uint16_t groupAddr, float value) {
    UiEvent event = {};
    event.type = UiEventType::KNX_VALUE;
    event.groupAddr = groupAddr;
    event.value = value;
    enqueueUiEvent(event);
}

void WidgetManager::onKnxSwitch(uint16_t groupAddr, bool value) {
    UiEvent event = {};
    event.type = UiEventType::KNX_SWITCH;
    event.groupAddr = groupAddr;
    event.state = value;
    enqueueUiEvent(event);
}

void WidgetManager::onKnxText(uint16_t groupAddr, const char* text) {
    UiEvent event = {};
    event.type = UiEventType::KNX_TEXT;
    event.groupAddr = groupAddr;
    if (text) {
        const size_t max_len = sizeof(event.text) - 1;
        const size_t src_len = strnlen(text, max_len);
        if (is_valid_utf8(text, src_len)) {
            memcpy(event.text, text, src_len);
            event.text[src_len] = '\0';
        } else {
            latin1_to_utf8(text, src_len, event.text, sizeof(event.text));
        }
    } else {
        event.text[0] = '\0';
    }
    enqueueUiEvent(event);
}

bool WidgetManager::enqueueUiEvent(const UiEvent& event) {
    if (!uiQueue_) return false;
    return xQueueSend(uiQueue_, &event, 0) == pdTRUE;
}

void WidgetManager::processUiQueue() {
    if (!uiQueue_) return;
    if (uxQueueMessagesWaiting(uiQueue_) == 0) return;

    if (esp_lv_adapter_lock(-1) != ESP_OK) return;

    UiEvent event = {};
    static constexpr size_t kMaxEventsPerLoop = 8;
    size_t processed = 0;
    while (processed < kMaxEventsPerLoop &&
           xQueueReceive(uiQueue_, &event, 0) == pdTRUE) {
        switch (event.type) {
            case UiEventType::KNX_VALUE:
                applyKnxValue(event.groupAddr, event.value);
                break;
            case UiEventType::KNX_SWITCH:
                applyKnxSwitch(event.groupAddr, event.state);
                break;
            case UiEventType::KNX_TEXT:
                applyKnxText(event.groupAddr, event.text);
                break;
        }
        processed++;
    }

    esp_lv_adapter_unlock();
}

void WidgetManager::applyKnxValue(uint16_t groupAddr, float value) {
    for (auto& widget : widgets_) {
        if (widget && widget->getKnxAddress() == groupAddr) {
            widget->onKnxValue(value);
        }
    }
}

void WidgetManager::applyKnxSwitch(uint16_t groupAddr, bool value) {
    for (auto& widget : widgets_) {
        if (widget && widget->getKnxAddress() == groupAddr) {
            widget->onKnxSwitch(value);
        }
    }
}

void WidgetManager::applyKnxText(uint16_t groupAddr, const char* text) {
    for (auto& widget : widgets_) {
        if (widget && widget->getKnxAddress() == groupAddr) {
            widget->onKnxText(text);
        }
    }
}

// Helper function to parse hex color string
static uint32_t parseHexColor(const char* colorStr) {
    if (!colorStr || colorStr[0] != '#') return 0;
    return strtoul(colorStr + 1, nullptr, 16);
}

void WidgetManager::getConfigJson(char* buf, size_t bufSize) const {
    cJSON* root = cJSON_CreateObject();
    if (!root) {
        snprintf(buf, bufSize, "{}");
        return;
    }

    cJSON_AddNumberToObject(root, "startScreen", config_.startScreenId);

    cJSON* standby = cJSON_AddObjectToObject(root, "standby");
    cJSON_AddBoolToObject(standby, "enabled", config_.standbyEnabled);
    cJSON_AddNumberToObject(standby, "screen", config_.standbyScreenId);
    cJSON_AddNumberToObject(standby, "minutes", config_.standbyMinutes);

    cJSON* screens = cJSON_AddArrayToObject(root, "screens");

    for (uint8_t s = 0; s < config_.screenCount; s++) {
        const ScreenConfig& screen = config_.screens[s];
        cJSON* screenJson = cJSON_CreateObject();

        cJSON_AddNumberToObject(screenJson, "id", screen.id);
        cJSON_AddStringToObject(screenJson, "name", screen.name);
        cJSON_AddNumberToObject(screenJson, "mode", static_cast<int>(screen.mode));

        char bgColorStr[8];
        snprintf(bgColorStr, sizeof(bgColorStr), "#%02X%02X%02X",
                 screen.backgroundColor.r, screen.backgroundColor.g, screen.backgroundColor.b);
        cJSON_AddStringToObject(screenJson, "bgColor", bgColorStr);

        // Modal-specific properties
        if (screen.mode == ScreenMode::MODAL) {
            cJSON* modal = cJSON_AddObjectToObject(screenJson, "modal");
            cJSON_AddNumberToObject(modal, "x", screen.modalX);
            cJSON_AddNumberToObject(modal, "y", screen.modalY);
            cJSON_AddNumberToObject(modal, "w", screen.modalWidth);
            cJSON_AddNumberToObject(modal, "h", screen.modalHeight);
            cJSON_AddNumberToObject(modal, "radius", screen.modalBorderRadius);
            cJSON_AddBoolToObject(modal, "dim", screen.modalDimBackground);
        }

        cJSON* widgets = cJSON_AddArrayToObject(screenJson, "widgets");
        for (uint8_t i = 0; i < screen.widgetCount; i++) {
            const WidgetConfig& w = screen.widgets[i];
            cJSON* widget = cJSON_CreateObject();

            cJSON_AddNumberToObject(widget, "id", w.id);
            cJSON_AddNumberToObject(widget, "type", static_cast<int>(w.type));
            cJSON_AddNumberToObject(widget, "x", w.x);
            cJSON_AddNumberToObject(widget, "y", w.y);
            cJSON_AddNumberToObject(widget, "w", w.width);
            cJSON_AddNumberToObject(widget, "h", w.height);
            cJSON_AddBoolToObject(widget, "visible", w.visible);
            cJSON_AddNumberToObject(widget, "textSrc", static_cast<int>(w.textSource));
            cJSON_AddStringToObject(widget, "text", w.text);
            cJSON_AddNumberToObject(widget, "knxAddr", w.knxAddress);
            cJSON_AddNumberToObject(widget, "fontSize", w.fontSize);
            cJSON_AddNumberToObject(widget, "textAlign", w.textAlign);
            cJSON_AddBoolToObject(widget, "isContainer", w.isContainer);

            char textColorStr[8];
            snprintf(textColorStr, sizeof(textColorStr), "#%02X%02X%02X",
                     w.textColor.r, w.textColor.g, w.textColor.b);
            cJSON_AddStringToObject(widget, "textColor", textColorStr);

            char widgetBgColorStr[8];
            snprintf(widgetBgColorStr, sizeof(widgetBgColorStr), "#%02X%02X%02X",
                     w.bgColor.r, w.bgColor.g, w.bgColor.b);
            cJSON_AddStringToObject(widget, "bgColor", widgetBgColorStr);

            cJSON_AddNumberToObject(widget, "bgOpacity", w.bgOpacity);
            cJSON_AddNumberToObject(widget, "radius", w.borderRadius);

            cJSON* shadow = cJSON_AddObjectToObject(widget, "shadow");
            cJSON_AddBoolToObject(shadow, "enabled", w.shadow.enabled);
            cJSON_AddNumberToObject(shadow, "x", w.shadow.offsetX);
            cJSON_AddNumberToObject(shadow, "y", w.shadow.offsetY);
            cJSON_AddNumberToObject(shadow, "blur", w.shadow.blur);
            cJSON_AddNumberToObject(shadow, "spread", w.shadow.spread);
            char shadowColorStr[8];
            snprintf(shadowColorStr, sizeof(shadowColorStr), "#%02X%02X%02X",
                     w.shadow.color.r, w.shadow.color.g, w.shadow.color.b);
            cJSON_AddStringToObject(shadow, "color", shadowColorStr);

            cJSON_AddBoolToObject(widget, "isToggle", w.isToggle);
            cJSON_AddNumberToObject(widget, "knxAddrWrite", w.knxAddressWrite);
            cJSON_AddNumberToObject(widget, "action", static_cast<int>(w.action));
            cJSON_AddNumberToObject(widget, "targetScreen", w.targetScreen);

            // Icon properties
            cJSON_AddNumberToObject(widget, "iconCodepoint", w.iconCodepoint);
            cJSON_AddNumberToObject(widget, "iconPosition", w.iconPosition);
            cJSON_AddNumberToObject(widget, "iconSize", w.iconSize);
            cJSON_AddNumberToObject(widget, "iconGap", w.iconGap);
            
            cJSON_AddNumberToObject(widget, "parentId", w.parentId);

            cJSON_AddItemToArray(widgets, widget);
        }

        cJSON_AddItemToArray(screens, screenJson);
    }

    char* jsonStr = cJSON_PrintUnformatted(root);
    if (jsonStr) {
        strncpy(buf, jsonStr, bufSize - 1);
        buf[bufSize - 1] = '\0';
        free(jsonStr);
    } else {
        snprintf(buf, bufSize, "{}");
    }

    cJSON_Delete(root);
}

bool WidgetManager::updateConfigFromJson(const char* json) {
    cJSON* root = cJSON_Parse(json);
    if (!root) {
        ESP_LOGE(TAG, "Failed to parse JSON");
        return false;
    }

    std::unique_ptr<GuiConfig> newConfig(new (std::nothrow) GuiConfig());
    if (!newConfig) {
        ESP_LOGE(TAG, "Out of memory for config");
        cJSON_Delete(root);
        return false;
    }
    newConfig->clear();

    auto parseWidgets = [&](cJSON* widgets, ScreenConfig& screen) -> bool {
        if (!cJSON_IsArray(widgets)) return false;

        screen.widgetCount = 0;
        cJSON* widget = nullptr;
        cJSON_ArrayForEach(widget, widgets) {
            if (screen.widgetCount >= MAX_WIDGETS) break;

            WidgetConfig& w = screen.widgets[screen.widgetCount];
            memset(&w, 0, sizeof(w));
            w.visible = true;
            w.action = ButtonAction::KNX;
            w.targetScreen = 0;
            w.textAlign = static_cast<uint8_t>(TextAlign::CENTER);
            w.isContainer = false;

            cJSON* id = cJSON_GetObjectItem(widget, "id");
            if (cJSON_IsNumber(id)) w.id = id->valueint;

            cJSON* type = cJSON_GetObjectItem(widget, "type");
            if (cJSON_IsNumber(type)) w.type = static_cast<WidgetType>(type->valueint);

            cJSON* x = cJSON_GetObjectItem(widget, "x");
            if (cJSON_IsNumber(x)) w.x = x->valueint;

            cJSON* y = cJSON_GetObjectItem(widget, "y");
            if (cJSON_IsNumber(y)) w.y = y->valueint;

            cJSON* width = cJSON_GetObjectItem(widget, "w");
            if (cJSON_IsNumber(width)) w.width = width->valueint;

            cJSON* height = cJSON_GetObjectItem(widget, "h");
            if (cJSON_IsNumber(height)) w.height = height->valueint;

            cJSON* visible = cJSON_GetObjectItem(widget, "visible");
            if (cJSON_IsBool(visible)) w.visible = cJSON_IsTrue(visible);

            cJSON* textSrc = cJSON_GetObjectItem(widget, "textSrc");
            if (cJSON_IsNumber(textSrc)) w.textSource = static_cast<TextSource>(textSrc->valueint);

            cJSON* text = cJSON_GetObjectItem(widget, "text");
            if (cJSON_IsString(text)) {
                strncpy(w.text, text->valuestring, MAX_TEXT_LEN - 1);
                w.text[MAX_TEXT_LEN - 1] = '\0';
            }

            cJSON* knxAddr = cJSON_GetObjectItem(widget, "knxAddr");
            if (cJSON_IsNumber(knxAddr)) w.knxAddress = knxAddr->valueint;

            cJSON* fontSize = cJSON_GetObjectItem(widget, "fontSize");
            if (cJSON_IsNumber(fontSize)) w.fontSize = fontSize->valueint;

            cJSON* textAlign = cJSON_GetObjectItem(widget, "textAlign");
            if (cJSON_IsNumber(textAlign)) w.textAlign = textAlign->valueint;

            cJSON* isContainer = cJSON_GetObjectItem(widget, "isContainer");
            if (cJSON_IsBool(isContainer)) w.isContainer = cJSON_IsTrue(isContainer);

            cJSON* textColor = cJSON_GetObjectItem(widget, "textColor");
            if (cJSON_IsString(textColor)) {
                w.textColor = Color::fromHex(parseHexColor(textColor->valuestring));
            }

            cJSON* widgetBgColor = cJSON_GetObjectItem(widget, "bgColor");
            if (cJSON_IsString(widgetBgColor)) {
                w.bgColor = Color::fromHex(parseHexColor(widgetBgColor->valuestring));
            }

            cJSON* bgOpacity = cJSON_GetObjectItem(widget, "bgOpacity");
            if (cJSON_IsNumber(bgOpacity)) w.bgOpacity = bgOpacity->valueint;

            cJSON* radius = cJSON_GetObjectItem(widget, "radius");
            if (cJSON_IsNumber(radius)) w.borderRadius = radius->valueint;

            cJSON* shadow = cJSON_GetObjectItem(widget, "shadow");
            if (cJSON_IsObject(shadow)) {
                cJSON* enabled = cJSON_GetObjectItem(shadow, "enabled");
                if (cJSON_IsBool(enabled)) w.shadow.enabled = cJSON_IsTrue(enabled);

                cJSON* sx = cJSON_GetObjectItem(shadow, "x");
                if (cJSON_IsNumber(sx)) w.shadow.offsetX = sx->valueint;

                cJSON* sy = cJSON_GetObjectItem(shadow, "y");
                if (cJSON_IsNumber(sy)) w.shadow.offsetY = sy->valueint;

                cJSON* blur = cJSON_GetObjectItem(shadow, "blur");
                if (cJSON_IsNumber(blur)) w.shadow.blur = blur->valueint;

                cJSON* spread = cJSON_GetObjectItem(shadow, "spread");
                if (cJSON_IsNumber(spread)) w.shadow.spread = spread->valueint;

                cJSON* shadowColor = cJSON_GetObjectItem(shadow, "color");
                if (cJSON_IsString(shadowColor)) {
                    w.shadow.color = Color::fromHex(parseHexColor(shadowColor->valuestring));
                }
            }

            cJSON* isToggle = cJSON_GetObjectItem(widget, "isToggle");
            if (cJSON_IsBool(isToggle)) w.isToggle = cJSON_IsTrue(isToggle);

            cJSON* knxAddrWrite = cJSON_GetObjectItem(widget, "knxAddrWrite");
            if (cJSON_IsNumber(knxAddrWrite)) w.knxAddressWrite = knxAddrWrite->valueint;

            cJSON* action = cJSON_GetObjectItem(widget, "action");
            if (cJSON_IsNumber(action)) w.action = static_cast<ButtonAction>(action->valueint);

            cJSON* targetScreen = cJSON_GetObjectItem(widget, "targetScreen");
            if (cJSON_IsNumber(targetScreen)) w.targetScreen = targetScreen->valueint;

            // Icon properties
            cJSON* iconCodepoint = cJSON_GetObjectItem(widget, "iconCodepoint");
            if (cJSON_IsNumber(iconCodepoint)) w.iconCodepoint = static_cast<uint32_t>(iconCodepoint->valuedouble);

            cJSON* iconPosition = cJSON_GetObjectItem(widget, "iconPosition");
            if (cJSON_IsNumber(iconPosition)) w.iconPosition = iconPosition->valueint;

            cJSON* iconSize = cJSON_GetObjectItem(widget, "iconSize");
            if (cJSON_IsNumber(iconSize)) w.iconSize = iconSize->valueint;

            cJSON* iconGap = cJSON_GetObjectItem(widget, "iconGap");
            if (cJSON_IsNumber(iconGap)) w.iconGap = iconGap->valueint;
            
            cJSON* parentId = cJSON_GetObjectItem(widget, "parentId");
            if (cJSON_IsNumber(parentId)) {
                w.parentId = static_cast<int8_t>(parentId->valueint);
            } else {
                w.parentId = -1; // Default to root
            }

            screen.widgetCount++;
        }

        return true;
    };

    cJSON* screens = cJSON_GetObjectItem(root, "screens");
    if (cJSON_IsArray(screens)) {
        cJSON* screenJson = nullptr;
        cJSON_ArrayForEach(screenJson, screens) {
            if (newConfig->screenCount >= MAX_SCREENS) break;

            uint8_t screenId = newConfig->screenCount;
            const char* screenName = nullptr;

            cJSON* id = cJSON_GetObjectItem(screenJson, "id");
            if (cJSON_IsNumber(id)) {
                int idVal = id->valueint;
                if (idVal < 0) idVal = 0;
                screenId = static_cast<uint8_t>(idVal);
            }

            cJSON* name = cJSON_GetObjectItem(screenJson, "name");
            if (cJSON_IsString(name)) screenName = name->valuestring;

            ScreenConfig& screen = newConfig->screens[newConfig->screenCount];
            screen.clear(screenId, screenName);

            if (!screen.name[0]) {
                char fallback[16];
                snprintf(fallback, sizeof(fallback), "Screen %d", screenId);
                strncpy(screen.name, fallback, sizeof(screen.name) - 1);
            }

            cJSON* mode = cJSON_GetObjectItem(screenJson, "mode");
            if (cJSON_IsNumber(mode)) screen.mode = static_cast<ScreenMode>(mode->valueint);

            cJSON* bgColor = cJSON_GetObjectItem(screenJson, "bgColor");
            if (cJSON_IsString(bgColor)) {
                screen.backgroundColor = Color::fromHex(parseHexColor(bgColor->valuestring));
            }

            // Parse modal-specific properties
            cJSON* modal = cJSON_GetObjectItem(screenJson, "modal");
            if (cJSON_IsObject(modal)) {
                cJSON* mx = cJSON_GetObjectItem(modal, "x");
                if (cJSON_IsNumber(mx)) screen.modalX = mx->valueint;

                cJSON* my = cJSON_GetObjectItem(modal, "y");
                if (cJSON_IsNumber(my)) screen.modalY = my->valueint;

                cJSON* mw = cJSON_GetObjectItem(modal, "w");
                if (cJSON_IsNumber(mw)) screen.modalWidth = mw->valueint;

                cJSON* mh = cJSON_GetObjectItem(modal, "h");
                if (cJSON_IsNumber(mh)) screen.modalHeight = mh->valueint;

                cJSON* mr = cJSON_GetObjectItem(modal, "radius");
                if (cJSON_IsNumber(mr)) screen.modalBorderRadius = mr->valueint;

                cJSON* dim = cJSON_GetObjectItem(modal, "dim");
                if (cJSON_IsBool(dim)) screen.modalDimBackground = cJSON_IsTrue(dim);
            }

            cJSON* widgets = cJSON_GetObjectItem(screenJson, "widgets");
            if (!parseWidgets(widgets, screen)) {
                screen.widgetCount = 0;
            }
            ensureButtonLabels(screen);

            newConfig->screenCount++;
        }
    } else {
        newConfig->screenCount = 1;
        ScreenConfig& screen = newConfig->screens[0];
        screen.clear(0, "Screen 1");

        cJSON* bgColor = cJSON_GetObjectItem(root, "bgColor");
        if (cJSON_IsString(bgColor)) {
            screen.backgroundColor = Color::fromHex(parseHexColor(bgColor->valuestring));
        }

        cJSON* widgets = cJSON_GetObjectItem(root, "widgets");
        if (!parseWidgets(widgets, screen)) {
            cJSON_Delete(root);
            return false;
        }
        ensureButtonLabels(screen);
    }

    cJSON* startScreen = cJSON_GetObjectItem(root, "startScreen");
    if (cJSON_IsNumber(startScreen)) {
        int val = startScreen->valueint;
        if (val < 0) val = 0;
        newConfig->startScreenId = static_cast<uint8_t>(val);
    }

    cJSON* standby = cJSON_GetObjectItem(root, "standby");
    if (cJSON_IsObject(standby)) {
        cJSON* enabled = cJSON_GetObjectItem(standby, "enabled");
        if (cJSON_IsBool(enabled)) newConfig->standbyEnabled = cJSON_IsTrue(enabled);

        cJSON* screen = cJSON_GetObjectItem(standby, "screen");
        if (cJSON_IsNumber(screen)) {
            int val = screen->valueint;
            if (val < 0) {
                newConfig->standbyScreenId = SCREEN_ID_NONE;
            } else {
                newConfig->standbyScreenId = static_cast<uint8_t>(val);
            }
        }

        cJSON* minutes = cJSON_GetObjectItem(standby, "minutes");
        if (cJSON_IsNumber(minutes)) {
            int val = minutes->valueint;
            if (val < 0) val = 0;
            newConfig->standbyMinutes = static_cast<uint16_t>(val);
        }
    }

    if (newConfig->screenCount == 0) {
        cJSON_Delete(root);
        return false;
    }

    if (!newConfig->findScreen(newConfig->startScreenId)) {
        newConfig->startScreenId = newConfig->screens[0].id;
    }

    if (!newConfig->findScreen(newConfig->standbyScreenId)) {
        newConfig->standbyEnabled = false;
        newConfig->standbyScreenId = SCREEN_ID_NONE;
    }

    config_ = *newConfig;
    cJSON_Delete(root);
    ESP_LOGI(TAG, "Parsed %d screens from JSON", config_.screenCount);
    return true;
}
