#include "LabelWidget.hpp"
#include "../Fonts.hpp"
#include <cstdio>
#include <cstring>

LabelWidget::LabelWidget(const WidgetConfig& config)
    : Widget(config)
    , container_(nullptr)
    , textLabel_(nullptr)
    , iconLabel_(nullptr)
{
}

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_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_label_text_if_changed(lv_obj_t* label, const char* text) {
    if (!label || !text) return;
    const char* current = lv_label_get_text(label);
    if (current && strcmp(current, text) == 0) return;
    lv_label_set_text(label, text);
}

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
}

int LabelWidget::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 LabelWidget::setupFlexLayout() {
    if (container_ == 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(container_,
        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(container_, 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(container_, gap, 0);
}

lv_obj_t* LabelWidget::create(lv_obj_t* parent) {
    bool hasIcon = config_.iconCodepoint > 0 && Fonts::hasIconFont();
    bool needsContainer = hasIcon || config_.bgOpacity > 0 || config_.borderRadius > 0;

    if (needsContainer) {
        // Create container object for background/radius/flex layout
        obj_ = lv_obj_create(parent);
        if (obj_ == nullptr) {
            return nullptr;
        }
        lv_obj_remove_style_all(obj_);
        lv_obj_set_pos(obj_, config_.x, config_.y);
        if (config_.width > 0 && config_.height > 0) {
            lv_obj_set_size(obj_, config_.width, config_.height);
        } else {
            lv_obj_set_size(obj_, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
        }

        container_ = obj_;
        set_obj_name(container_, "Label", config_.id, "container");

        if (hasIcon) {
            // Create elements in correct order based on icon position
            bool iconFirst = (config_.iconPosition == static_cast<uint8_t>(IconPosition::LEFT) ||
                              config_.iconPosition == static_cast<uint8_t>(IconPosition::TOP));

            if (iconFirst) {
                iconLabel_ = lv_label_create(container_);
                set_obj_name(iconLabel_, "Label", 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);
            }

            textLabel_ = lv_label_create(container_);
            set_obj_name(textLabel_, "Label", config_.id, "text");
            lv_label_set_text(textLabel_, config_.text);
            lv_obj_clear_flag(textLabel_, LV_OBJ_FLAG_CLICKABLE);

            if (!iconFirst) {
                iconLabel_ = lv_label_create(container_);
                set_obj_name(iconLabel_, "Label", 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 {
            // Container with just text label (for bg/radius)
            textLabel_ = lv_label_create(container_);
            set_obj_name(textLabel_, "Label", config_.id, "text");
            lv_label_set_text(textLabel_, config_.text);
            lv_obj_center(textLabel_);
            lv_obj_clear_flag(textLabel_, LV_OBJ_FLAG_CLICKABLE);
        }
    } else {
        // Simple label without container
        obj_ = lv_label_create(parent);
        textLabel_ = obj_;
        set_obj_name(obj_, "Label", config_.id, nullptr);
        lv_label_set_text(obj_, config_.text);
        lv_obj_set_pos(obj_, config_.x, config_.y);
        if (config_.width > 0 && config_.height > 0) {
            lv_obj_set_size(obj_, config_.width, config_.height);
        }
    }

    if (obj_ != nullptr) {
        lv_obj_clear_flag(obj_, LV_OBJ_FLAG_CLICKABLE);

        // Initial update for system time widgets to avoid empty display
        if (config_.textSource == TextSource::SYSTEM_TIME ||
            config_.textSource == TextSource::SYSTEM_DATE ||
            config_.textSource == TextSource::SYSTEM_DATETIME) {
            time_t now;
            time(&now);
            struct tm timeinfo;
            localtime_r(&now, &timeinfo);
            onKnxTime(timeinfo, config_.textSource);
        }
    }
    return obj_;
}

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

    // Apply background and border radius if container exists
    if (container_ != nullptr) {
        if (config_.bgOpacity > 0) {
            lv_obj_set_style_bg_color(obj_, lv_color_make(
                config_.bgColor.r, config_.bgColor.g, config_.bgColor.b), 0);
            lv_obj_set_style_bg_opa(obj_, config_.bgOpacity, 0);
        }
        if (config_.borderRadius > 0) {
            lv_obj_set_style_radius(obj_, config_.borderRadius, 0);
        }
        if (config_.borderWidth > 0 && config_.borderOpacity > 0) {
            lv_obj_set_style_border_width(obj_, config_.borderWidth, 0);
            lv_obj_set_style_border_color(obj_, lv_color_make(
                config_.borderColor.r, config_.borderColor.g, config_.borderColor.b), 0);
            lv_obj_set_style_border_opa(obj_, config_.borderOpacity, 0);
        } else {
            lv_obj_set_style_border_width(obj_, 0, 0);
        }
        applyShadowStyle();
    }

    // Apply text style
    if (textLabel_ != nullptr) {
        lv_obj_set_style_text_color(textLabel_, lv_color_make(
            config_.textColor.r, config_.textColor.g, config_.textColor.b), 0);
        lv_obj_set_style_text_font(textLabel_, Fonts::bySizeIndex(config_.fontSize), 0);
        lv_obj_set_style_text_align(textLabel_, toLvTextAlign(config_.textAlign), 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);

        uint8_t sizeIdx = config_.iconSize < 6 ? config_.iconSize : config_.fontSize;
        lv_obj_set_style_text_font(iconLabel_, Fonts::iconFont(sizeIdx), 0);
    }

    // For simple label (no container), apply common style
    if (container_ == nullptr) {
        applyCommonStyle();
    }
}

void LabelWidget::onKnxValue(float value) {
    lv_obj_t* label = textLabel_ ? textLabel_ : obj_;
    if (label == nullptr) return;
    if (config_.textSource != TextSource::KNX_DPT_TEMP &&
        config_.textSource != TextSource::KNX_DPT_PERCENT &&
        config_.textSource != TextSource::KNX_DPT_POWER &&
        config_.textSource != TextSource::KNX_DPT_ENERGY &&
        config_.textSource != TextSource::KNX_DPT_DECIMALFACTOR) {
        return;
    }

    char buf[32];
    if (config_.textSource == TextSource::KNX_DPT_PERCENT ||
        config_.textSource == TextSource::KNX_DPT_DECIMALFACTOR) {
        int intValue = static_cast<int>(value + 0.5f);
        snprintf(buf, sizeof(buf), config_.text, intValue);
    } else {
        snprintf(buf, sizeof(buf), config_.text, value);
    }
    set_label_text_if_changed(label, buf);
}

void LabelWidget::onKnxSwitch(bool value) {
    lv_obj_t* label = textLabel_ ? textLabel_ : obj_;
    if (label == nullptr) return;
    if (config_.textSource != TextSource::KNX_DPT_SWITCH) return;

    set_label_text_if_changed(label, value ? "EIN" : "AUS");
}

void LabelWidget::onKnxText(const char* text) {
    lv_obj_t* label = textLabel_ ? textLabel_ : obj_;
    if (label == nullptr) return;
    if (config_.textSource != TextSource::KNX_DPT_TEXT) return;

    set_label_text_if_changed(label, text);
}

void LabelWidget::onKnxTime(const struct tm& value, TextSource source) {
    lv_obj_t* label = textLabel_ ? textLabel_ : obj_;
    if (label == nullptr) return;
    if (config_.textSource != source) return;
    if (source != TextSource::KNX_DPT_TIME &&
        source != TextSource::KNX_DPT_DATE &&
        source != TextSource::KNX_DPT_DATETIME &&
        source != TextSource::SYSTEM_TIME &&
        source != TextSource::SYSTEM_DATE &&
        source != TextSource::SYSTEM_DATETIME) {
        return;
    }

    int year = value.tm_year;
    // tm_year is usually year-1900.
    // If value comes from KNX raw decode, it might be full year depending on logic.
    // Standard tm_year: 123 for 2023.
    // Our existing logic handled year < 1900 by adding 1900.
    // Let's ensure consistent behavior for SYSTEM time which uses standard tm struct.
    if (year > 0 && year < 1900) {
        year += 1900;
    }
    int month = value.tm_mon;
    if (month < 1 || month > 12) {
        // tm_mon is 0-11
        if (month >= 0 && month <= 11) {
            month += 1;
        }
    }

    const char* fmt = config_.text;
    if (!fmt || fmt[0] == '\0' || strchr(fmt, '%') == nullptr) {
        if (source == TextSource::KNX_DPT_TIME || source == TextSource::SYSTEM_TIME) {
            fmt = "%02d:%02d:%02d";
        } else if (source == TextSource::KNX_DPT_DATE || source == TextSource::SYSTEM_DATE) {
            fmt = "%02d.%02d.%04d";
        } else {
            fmt = "%02d.%02d.%04d %02d:%02d:%02d";
        }
    }

    char buf[32];
    if (source == TextSource::KNX_DPT_TIME || source == TextSource::SYSTEM_TIME) {
        snprintf(buf, sizeof(buf), fmt, value.tm_hour, value.tm_min, value.tm_sec);
    } else if (source == TextSource::KNX_DPT_DATE || source == TextSource::SYSTEM_DATE) {
        snprintf(buf, sizeof(buf), fmt, value.tm_mday, month, year);
    } else {
        snprintf(buf, sizeof(buf), fmt, value.tm_mday, month, year,
                 value.tm_hour, value.tm_min, value.tm_sec);
    }
    set_label_text_if_changed(label, buf);
}
