#include "ButtonWidget.hpp"
#include "../WidgetManager.hpp"
#include "../Fonts.hpp"
#include "esp_log.h"
#include <cstdio>

static const char* TAG = "ButtonWidget";

static lv_text_align_t toLvTextAlign(uint8_t align) {
    if (align == static_cast<uint8_t>(TextAlign::LEFT)) return LV_TEXT_ALIGN_LEFT;
    if (align == static_cast<uint8_t>(TextAlign::RIGHT)) return LV_TEXT_ALIGN_RIGHT;
    return LV_TEXT_ALIGN_CENTER;
}

static lv_align_t toLvAlign(uint8_t align) {
    if (align == static_cast<uint8_t>(TextAlign::LEFT)) return LV_ALIGN_LEFT_MID;
    if (align == static_cast<uint8_t>(TextAlign::RIGHT)) return LV_ALIGN_RIGHT_MID;
    return LV_ALIGN_CENTER;
}

static lv_flex_align_t toFlexAlign(uint8_t align) {
    if (align == static_cast<uint8_t>(TextAlign::LEFT)) return LV_FLEX_ALIGN_START;
    if (align == static_cast<uint8_t>(TextAlign::RIGHT)) return LV_FLEX_ALIGN_END;
    return LV_FLEX_ALIGN_CENTER;
}

static void set_obj_name(lv_obj_t* obj, const char* base, uint8_t id, const char* suffix) {
#if LV_USE_OBJ_NAME
    if (!obj || !base) return;
    char name[48];
    if (suffix && suffix[0] != '\0') {
        snprintf(name, sizeof(name), "%s#%u_%s", base, id, suffix);
    } else {
        snprintf(name, sizeof(name), "%s#%u", base, id);
    }
    lv_obj_set_name(obj, name);
#else
    (void)obj;
    (void)base;
    (void)id;
    (void)suffix;
#endif
}

ButtonWidget::ButtonWidget(const WidgetConfig& config)
    : Widget(config)
    , contentContainer_(nullptr)
    , label_(nullptr)
    , iconLabel_(nullptr)
    , shadowObj_(nullptr)
{
}

ButtonWidget::~ButtonWidget() {
    // Remove event callback BEFORE the base class destructor deletes the object
    if (obj_) {
        lv_obj_remove_event_cb(obj_, clickCallback);
    }
    // Delete fake shadow (not a child of obj_, so must delete separately)
    if (shadowObj_ && lv_obj_is_valid(shadowObj_)) {
        lv_obj_delete(shadowObj_);
    }
    shadowObj_ = nullptr;
    contentContainer_ = nullptr;
    label_ = nullptr;
    iconLabel_ = nullptr;
}

int ButtonWidget::encodeUtf8(uint32_t codepoint, char* buf) {
    if (codepoint < 0x80) {
        buf[0] = static_cast<char>(codepoint);
        buf[1] = '\0';
        return 1;
    } else if (codepoint < 0x800) {
        buf[0] = static_cast<char>(0xC0 | (codepoint >> 6));
        buf[1] = static_cast<char>(0x80 | (codepoint & 0x3F));
        buf[2] = '\0';
        return 2;
    } else if (codepoint < 0x10000) {
        buf[0] = static_cast<char>(0xE0 | (codepoint >> 12));
        buf[1] = static_cast<char>(0x80 | ((codepoint >> 6) & 0x3F));
        buf[2] = static_cast<char>(0x80 | (codepoint & 0x3F));
        buf[3] = '\0';
        return 3;
    } else if (codepoint < 0x110000) {
        buf[0] = static_cast<char>(0xF0 | (codepoint >> 18));
        buf[1] = static_cast<char>(0x80 | ((codepoint >> 12) & 0x3F));
        buf[2] = static_cast<char>(0x80 | ((codepoint >> 6) & 0x3F));
        buf[3] = static_cast<char>(0x80 | (codepoint & 0x3F));
        buf[4] = '\0';
        return 4;
    }
    buf[0] = '\0';
    return 0;
}

void ButtonWidget::clickCallback(lv_event_t* e) {
    printf("ButtonWidget::clickCallback\n"); 
    fflush(stdout);
    ButtonWidget* widget = static_cast<ButtonWidget*>(lv_event_get_user_data(e));
    if (!widget) {
        printf("ButtonWidget: Widget is null!\n");
        fflush(stdout);
        return;
    }
    lv_obj_t* target = static_cast<lv_obj_t*>(lv_event_get_target(e));
    WidgetManager::instance().handleButtonAction(widget->getConfig(), target);
}

void ButtonWidget::setupFlexLayout() {
    if (contentContainer_ == nullptr) return;

    // Determine flex direction based on icon position
    bool isVertical = (config_.iconPosition == static_cast<uint8_t>(IconPosition::TOP) ||
                       config_.iconPosition == static_cast<uint8_t>(IconPosition::BOTTOM));

    lv_obj_set_flex_flow(contentContainer_,
        isVertical ? LV_FLEX_FLOW_COLUMN : LV_FLEX_FLOW_ROW);
    lv_flex_align_t mainAlign = LV_FLEX_ALIGN_CENTER;
    lv_flex_align_t crossAlign = LV_FLEX_ALIGN_CENTER;
    lv_flex_align_t contentAlign = toFlexAlign(config_.textAlign);
    if (contentAlign != LV_FLEX_ALIGN_CENTER) {
        if (isVertical) {
            crossAlign = contentAlign;
        } else {
            mainAlign = contentAlign;
        }
    }
    lv_obj_set_flex_align(contentContainer_, mainAlign, crossAlign, LV_FLEX_ALIGN_CENTER);

    // Set gap between icon and text
    int gap = config_.iconGap > 0 ? config_.iconGap : 8;
    lv_obj_set_style_pad_gap(contentContainer_, gap, 0);
}

void ButtonWidget::applyTextAlignment() {
    if (label_ == nullptr) return;
    lv_obj_set_style_text_align(label_, toLvTextAlign(config_.textAlign), 0);
    lv_obj_align(label_, toLvAlign(config_.textAlign), 0, 0);
}

lv_obj_t* ButtonWidget::create(lv_obj_t* parent) {
    // Create fake shadow FIRST (so it's behind the button in z-order)
    if (config_.shadow.enabled) {
        createFakeShadow(parent);
    }

    obj_ = lv_btn_create(parent);
    lv_obj_set_pos(obj_, config_.x, config_.y);
    lv_obj_set_size(obj_, config_.width > 0 ? config_.width : 100,
                          config_.height > 0 ? config_.height : 50);
    set_obj_name(obj_, "Button", config_.id, nullptr);

    lv_obj_add_event_cb(obj_, clickCallback, LV_EVENT_CLICKED, this);

    if (config_.isContainer) {
        ESP_LOGI(TAG, "Created container button %d", config_.id);
        return obj_;
    }

    bool hasIcon = config_.iconCodepoint > 0 && Fonts::hasIconFont();

    if (hasIcon) {
        // Create container for flex layout
        contentContainer_ = lv_obj_create(obj_);
        if (contentContainer_) {
            set_obj_name(contentContainer_, "Button", config_.id, "content");
            lv_obj_remove_style_all(contentContainer_);
            lv_obj_set_size(contentContainer_, LV_PCT(100), LV_PCT(100));
            lv_obj_center(contentContainer_);
            lv_obj_clear_flag(contentContainer_, LV_OBJ_FLAG_CLICKABLE);

            bool iconFirst = (config_.iconPosition == static_cast<uint8_t>(IconPosition::LEFT) ||
                              config_.iconPosition == static_cast<uint8_t>(IconPosition::TOP));

            if (iconFirst) {
                iconLabel_ = lv_label_create(contentContainer_);
                set_obj_name(iconLabel_, "Button", config_.id, "icon");
                char iconText[5];
                encodeUtf8(config_.iconCodepoint, iconText);
                lv_label_set_text(iconLabel_, iconText);
                lv_obj_clear_flag(iconLabel_, LV_OBJ_FLAG_CLICKABLE);
            }

            label_ = lv_label_create(contentContainer_);
            set_obj_name(label_, "Button", config_.id, "text");
            lv_label_set_text(label_, config_.text);
            lv_obj_clear_flag(label_, LV_OBJ_FLAG_CLICKABLE);

            if (!iconFirst) {
                iconLabel_ = lv_label_create(contentContainer_);
                set_obj_name(iconLabel_, "Button", config_.id, "icon");
                char iconText[5];
                encodeUtf8(config_.iconCodepoint, iconText);
                lv_label_set_text(iconLabel_, iconText);
                lv_obj_clear_flag(iconLabel_, LV_OBJ_FLAG_CLICKABLE);
            }

            setupFlexLayout();
        }
    } else {
        // Simple button without icon
        label_ = lv_label_create(obj_);
        set_obj_name(label_, "Button", config_.id, "text");
        lv_label_set_text(label_, config_.text);
        lv_label_set_long_mode(label_, LV_LABEL_LONG_WRAP);
        lv_obj_set_width(label_, LV_SIZE_CONTENT);
        applyTextAlignment();
        lv_obj_clear_flag(label_, LV_OBJ_FLAG_CLICKABLE);
    }

    ESP_LOGI(TAG, "Created button '%s' at %d,%d (icon: 0x%lx)",
             config_.text, config_.x, config_.y, (unsigned long)config_.iconCodepoint);
    return obj_;
}

void ButtonWidget::applyStyle() {
    if (obj_ == nullptr) return;

    // Apply common style to button (shadows skipped for buttons in Widget::applyShadowStyle)
    applyCommonStyle();

    // Apply fake shadow style
    applyFakeShadowStyle();

    // Apply text style to label
    if (label_ != nullptr) {
        lv_obj_set_style_text_color(label_, lv_color_make(
            config_.textColor.r, config_.textColor.g, config_.textColor.b), 0);
        lv_obj_set_style_text_font(label_, getFontBySize(config_.fontSize), 0);
    }

    // Apply icon style
    if (iconLabel_ != nullptr) {
        lv_obj_set_style_text_color(iconLabel_, lv_color_make(
            config_.textColor.r, config_.textColor.g, config_.textColor.b), 0);

        // Icon fonts support sizes 0-13 (14px to 260px)
        uint8_t sizeIdx = config_.iconSize;
        if (sizeIdx > 13) sizeIdx = 13;
        lv_obj_set_style_text_font(iconLabel_, Fonts::iconFont(sizeIdx), 0);
    }
}

void ButtonWidget::onKnxSwitch(bool value) {
    cachedPrimaryValue_ = value ? 1.0f : 0.0f;
    hasCachedPrimary_ = true;

    if (obj_ && config_.isToggle) {
        if (value) {
            lv_obj_add_state(obj_, LV_STATE_CHECKED);
        } else {
            lv_obj_clear_state(obj_, LV_STATE_CHECKED);
        }
    }

    evaluateConditions(cachedPrimaryValue_, cachedSecondaryValue_, cachedTertiaryValue_);
}

bool ButtonWidget::evaluateConditions(float primaryValue, float secondaryValue, float tertiaryValue) {
    if (config_.conditionCount == 0) return false;

    const StyleCondition* bestMatch = nullptr;
    uint8_t bestPriority = 255;

    for (uint8_t i = 0; i < config_.conditionCount && i < MAX_CONDITIONS; ++i) {
        const StyleCondition& cond = config_.conditions[i];
        if (!cond.enabled) continue;

        float checkValue = 0.0f;
        bool hasValue = false;
        switch (cond.source) {
            case ConditionSource::PRIMARY:
                checkValue = primaryValue;
                hasValue = hasCachedPrimary_;
                break;
            case ConditionSource::SECONDARY:
                checkValue = secondaryValue;
                hasValue = hasCachedSecondary_;
                break;
            case ConditionSource::TERTIARY:
                checkValue = tertiaryValue;
                hasValue = hasCachedTertiary_;
                break;
        }

        if (!hasValue) continue;

        bool matches = false;
        switch (cond.op) {
            case ConditionOp::LESS:
                matches = checkValue < cond.threshold;
                break;
            case ConditionOp::LESS_EQUAL:
                matches = checkValue <= cond.threshold;
                break;
            case ConditionOp::EQUAL:
                matches = checkValue == cond.threshold;
                break;
            case ConditionOp::GREATER_EQUAL:
                matches = checkValue >= cond.threshold;
                break;
            case ConditionOp::GREATER:
                matches = checkValue > cond.threshold;
                break;
            case ConditionOp::NOT_EQUAL:
                matches = checkValue != cond.threshold;
                break;
        }

        if (matches && cond.priority < bestPriority) {
            bestMatch = &cond;
            bestPriority = cond.priority;
        }
    }

    if (!bestMatch) {
        return false;
    }

    if (bestMatch->style.iconCodepoint != 0 &&
        bestMatch->style.iconCodepoint != currentConditionIcon_) {
        updateIcon(bestMatch->style.iconCodepoint);
        currentConditionIcon_ = bestMatch->style.iconCodepoint;
    }

    if (bestMatch->style.flags & ConditionStyle::FLAG_USE_TEXT_COLOR) {
        lv_color_t color = lv_color_make(
            bestMatch->style.textColor.r,
            bestMatch->style.textColor.g,
            bestMatch->style.textColor.b);
        if (label_) {
            lv_obj_set_style_text_color(label_, color, 0);
        }
        if (iconLabel_) {
            lv_obj_set_style_text_color(iconLabel_, color, 0);
        }
    }

    if (bestMatch->style.flags & ConditionStyle::FLAG_USE_BG_COLOR) {
        lv_color_t bgColor = lv_color_make(
            bestMatch->style.bgColor.r,
            bestMatch->style.bgColor.g,
            bestMatch->style.bgColor.b);
        // Set for default state
        lv_obj_set_style_bg_color(obj_, bgColor, LV_PART_MAIN | LV_STATE_DEFAULT);
        // Also set for checked state (toggle buttons)
        lv_obj_set_style_bg_color(obj_, bgColor, LV_PART_MAIN | LV_STATE_CHECKED);
        // And for pressed state
        lv_obj_set_style_bg_color(obj_, bgColor, LV_PART_MAIN | LV_STATE_PRESSED);
    }

    if (bestMatch->style.flags & ConditionStyle::FLAG_USE_BG_OPACITY) {
        lv_obj_set_style_bg_opa(obj_, bestMatch->style.bgOpacity, 0);
    }

    if (bestMatch->style.flags & ConditionStyle::FLAG_HIDE) {
        lv_obj_add_flag(obj_, LV_OBJ_FLAG_HIDDEN);
        if (shadowObj_ && lv_obj_is_valid(shadowObj_)) {
            lv_obj_add_flag(shadowObj_, LV_OBJ_FLAG_HIDDEN);
        }
    } else {
        lv_obj_clear_flag(obj_, LV_OBJ_FLAG_HIDDEN);
        if (shadowObj_ && lv_obj_is_valid(shadowObj_)) {
            lv_obj_clear_flag(shadowObj_, LV_OBJ_FLAG_HIDDEN);
        }
    }

    return true;
}

bool ButtonWidget::isChecked() const {
    if (obj_ == nullptr) return false;
    return (lv_obj_get_state(obj_) & LV_STATE_CHECKED) != 0;
}

void ButtonWidget::createFakeShadow(lv_obj_t* parent) {
    // Create a simple rectangle as fake shadow (no LVGL shadow rendering)
    shadowObj_ = lv_obj_create(parent);
    if (!shadowObj_) return;

    lv_obj_remove_style_all(shadowObj_);
    lv_obj_clear_flag(shadowObj_, static_cast<lv_obj_flag_t>(LV_OBJ_FLAG_CLICKABLE | LV_OBJ_FLAG_SCROLLABLE));

    // Position: offset from button position by shadow offset
    int16_t shadowX = config_.x + config_.shadow.offsetX;
    int16_t shadowY = config_.y + config_.shadow.offsetY;

    // Size: slightly larger than button (spread effect)
    int16_t spread = config_.shadow.spread > 8 ? 8 : config_.shadow.spread;
    int16_t shadowW = (config_.width > 0 ? config_.width : 100) + spread * 2;
    int16_t shadowH = (config_.height > 0 ? config_.height : 50) + spread * 2;

    // Adjust position for spread
    shadowX -= spread;
    shadowY -= spread;

    lv_obj_set_pos(shadowObj_, shadowX, shadowY);
    lv_obj_set_size(shadowObj_, shadowW, shadowH);
}

void ButtonWidget::applyFakeShadowStyle() {
    if (!shadowObj_) return;

    lv_color_t shadowColor = lv_color_make(
        config_.shadow.color.r, config_.shadow.color.g, config_.shadow.color.b);

    // Blur simulation: use lower opacity for softer look
    uint8_t blur = config_.shadow.blur > 15 ? 15 : config_.shadow.blur;
    uint8_t opa = blur > 0 ? (uint8_t)(120 - blur * 4) : 120;  // Less blur = more solid

    lv_obj_set_style_bg_color(shadowObj_, shadowColor, 0);
    lv_obj_set_style_bg_opa(shadowObj_, opa, 0);

    // Match button's border radius
    if (config_.borderRadius > 0) {
        lv_obj_set_style_radius(shadowObj_, config_.borderRadius + config_.shadow.spread, 0);
    }
}

void ButtonWidget::updateIcon(uint32_t codepoint) {
    if (codepoint == 0) return;

    // If no icon label exists yet, we need to create it dynamically
    if (!iconLabel_ && obj_ && Fonts::hasIconFont()) {
        // For buttons without a content container, we need to create the icon label
        if (!contentContainer_) {
            // Create a simple icon label directly in the button
            iconLabel_ = lv_label_create(obj_);
            if (iconLabel_) {
                lv_obj_clear_flag(iconLabel_, LV_OBJ_FLAG_CLICKABLE);
                // Position it based on icon position setting
                if (config_.iconPosition == static_cast<uint8_t>(IconPosition::LEFT)) {
                    lv_obj_align(iconLabel_, LV_ALIGN_LEFT_MID, config_.iconPositionX, 0);
                } else if (config_.iconPosition == static_cast<uint8_t>(IconPosition::RIGHT)) {
                    lv_obj_align(iconLabel_, LV_ALIGN_RIGHT_MID, -config_.iconPositionX, 0);
                } else {
                    lv_obj_align(iconLabel_, LV_ALIGN_CENTER, 0, 0);
                }
            }
        }
    }

    if (!iconLabel_) return;

    // Set the icon text
    char iconText[5];
    encodeUtf8(codepoint, iconText);
    lv_label_set_text(iconLabel_, iconText);

    // Apply the correct font size
    // If iconSize is 0 (no icon was originally configured), use fontSize instead
    // Default to size 2 (22px) if both are 0
    // Icon fonts support sizes 0-13 (14px to 260px)
    uint8_t sizeIdx = config_.iconSize > 0 ? config_.iconSize : config_.fontSize;
    if (sizeIdx == 0) sizeIdx = 2;  // Default to medium size (22px)
    if (sizeIdx > 13) sizeIdx = 13; // Cap at max icon font size
    lv_obj_set_style_text_font(iconLabel_, Fonts::iconFont(sizeIdx), 0);

    // Apply text color from config
    lv_obj_set_style_text_color(iconLabel_, lv_color_make(
        config_.textColor.r, config_.textColor.g, config_.textColor.b), 0);
}
