#include "ArcWidget.hpp"

#include <algorithm>
#include <cmath>
#include <cstdio>

ArcWidget::ArcWidget(const WidgetConfig& config)
    : Widget(config)
{
}

static int32_t clampToRange(float value, int32_t minValue, int32_t maxValue) {
    if (maxValue < minValue) std::swap(maxValue, minValue);
    if (value < minValue) value = static_cast<float>(minValue);
    if (value > maxValue) value = static_cast<float>(maxValue);
    return static_cast<int32_t>(std::lround(value));
}

lv_obj_t* ArcWidget::create(lv_obj_t* parent) {
    obj_ = lv_arc_create(parent);
    if (!obj_) return nullptr;

    lv_obj_set_pos(obj_, config_.x, config_.y);
    lv_obj_set_size(obj_,
                    config_.width > 0 ? config_.width : 180,
                    config_.height > 0 ? config_.height : 180);
    lv_obj_clear_flag(obj_, LV_OBJ_FLAG_SCROLLABLE);
    lv_obj_remove_flag(obj_, LV_OBJ_FLAG_CLICKABLE);
    lv_obj_add_flag(obj_, LV_OBJ_FLAG_OVERFLOW_VISIBLE);
    lv_obj_set_style_pad_all(obj_, 0, 0);

    lv_arc_set_rotation(obj_, 0);
    lv_arc_set_mode(obj_, LV_ARC_MODE_NORMAL);
    lv_arc_set_bg_angles(obj_, 135, 45);

    int32_t minValue = config_.arcMin;
    int32_t maxValue = config_.arcMax;
    if (maxValue <= minValue) {
        minValue = 0;
        maxValue = 100;
    }
    lv_arc_set_range(obj_, minValue, maxValue);

    lastValue_ = static_cast<float>(minValue);
    lv_arc_set_value(obj_, minValue);

    // Create scale as child of arc
    scale_ = lv_scale_create(obj_);
    if (scale_) {
        lv_obj_clear_flag(scale_, LV_OBJ_FLAG_SCROLLABLE);
        lv_obj_remove_flag(scale_, LV_OBJ_FLAG_CLICKABLE);
        lv_obj_add_flag(scale_, LV_OBJ_FLAG_OVERFLOW_VISIBLE);
        lv_obj_set_style_bg_opa(scale_, LV_OPA_TRANSP, 0);
        lv_obj_set_style_border_width(scale_, 0, 0);
        lv_obj_set_style_pad_all(scale_, 0, 0);
    }

    valueLabel_ = lv_label_create(obj_);
    if (valueLabel_) {
        lv_obj_center(valueLabel_);
        updateValueLabel(lastValue_);
    }

    return obj_;
}

void ArcWidget::applyStyle() {
    if (!obj_) return;

    applyCommonStyle();
    lv_obj_set_style_bg_opa(obj_, LV_OPA_TRANSP, 0);

    int32_t minValue = config_.arcMin;
    int32_t maxValue = config_.arcMax;
    if (maxValue <= minValue) {
        minValue = 0;
        maxValue = 100;
    }
    lv_arc_set_range(obj_, minValue, maxValue);

    int16_t arcWidth = config_.borderRadius > 0 ? static_cast<int16_t>(config_.borderRadius) : 12;
    if (arcWidth < 2) arcWidth = 2;
    if (arcWidth > 48) arcWidth = 48;

    Color trackCol = themeColor(config_.bgColor);
    lv_color_t trackColor = lv_color_make(trackCol.r, trackCol.g, trackCol.b);
    uint8_t trackOpa = config_.bgOpacity > 0 ? config_.bgOpacity : static_cast<uint8_t>(LV_OPA_40);

    Color indicatorCol = themeColor(config_.textColor);
    lv_color_t indicatorColor = lv_color_make(indicatorCol.r, indicatorCol.g, indicatorCol.b);

    lv_obj_set_style_arc_width(obj_, arcWidth, LV_PART_MAIN);
    lv_obj_set_style_arc_color(obj_, trackColor, LV_PART_MAIN);
    lv_obj_set_style_arc_opa(obj_, trackOpa, LV_PART_MAIN);
    lv_obj_set_style_arc_rounded(obj_, true, LV_PART_MAIN);

    lv_obj_set_style_arc_width(obj_, arcWidth, LV_PART_INDICATOR);
    lv_obj_set_style_arc_color(obj_, indicatorColor, LV_PART_INDICATOR);
    lv_obj_set_style_arc_opa(obj_, LV_OPA_COVER, LV_PART_INDICATOR);
    lv_obj_set_style_arc_rounded(obj_, true, LV_PART_INDICATOR);

    int16_t knobSize = std::max<int16_t>(8, static_cast<int16_t>(arcWidth + 6));
    lv_obj_set_style_arc_width(obj_, knobSize, LV_PART_KNOB);
    lv_obj_set_style_bg_color(obj_, indicatorColor, LV_PART_KNOB);
    lv_obj_set_style_bg_opa(obj_, LV_OPA_COVER, LV_PART_KNOB);

    if (scale_) {
        // Use config dimensions directly, not from arc object (which may have content area limitations)
        int16_t scaleWidth = config_.width > 0 ? static_cast<int16_t>(config_.width) : 180;
        int16_t scaleHeight = config_.height > 0 ? static_cast<int16_t>(config_.height) : 180;

        lv_obj_set_size(scale_, scaleWidth, scaleHeight);
        lv_obj_set_pos(scale_, 0, 0);
        lv_obj_set_align(scale_, LV_ALIGN_CENTER);

        lv_scale_set_mode(scale_, LV_SCALE_MODE_ROUND_INNER);
        lv_scale_set_angle_range(scale_, 270);
        lv_scale_set_rotation(scale_, 135);
        lv_scale_set_range(scale_, minValue, maxValue);
        lv_scale_set_total_tick_count(scale_, 21);
        lv_scale_set_major_tick_every(scale_, 5);
        lv_scale_set_label_show(scale_, true);

        int16_t majorLen = std::max<int16_t>(3, static_cast<int16_t>(arcWidth / 3 + 1));
        int16_t minorLen = std::max<int16_t>(2, static_cast<int16_t>(arcWidth / 4 + 1));
        lv_obj_set_style_length(scale_, majorLen, LV_PART_INDICATOR);
        lv_obj_set_style_length(scale_, minorLen, LV_PART_ITEMS);

        // Use radial_offset to position ticks at inner edge of arc stroke.
        // Positive arcScaleOffset should move the scale outward.
        int16_t scaleOffset = static_cast<int16_t>(-config_.arcScaleOffset);
        int16_t tickOffset = static_cast<int16_t>(-arcWidth + scaleOffset);
        lv_obj_set_style_radial_offset(scale_, tickOffset, LV_PART_INDICATOR);
        lv_obj_set_style_radial_offset(scale_, tickOffset, LV_PART_ITEMS);

        lv_obj_set_style_arc_width(scale_, 1, LV_PART_MAIN);
        lv_obj_set_style_arc_opa(scale_, LV_OPA_TRANSP, LV_PART_MAIN);

        Color scaleCol = themeColor(config_.arcScaleColor);
        lv_color_t scaleColor = lv_color_make(scaleCol.r, scaleCol.g, scaleCol.b);
        lv_obj_set_style_line_color(scale_, scaleColor, LV_PART_INDICATOR);
        lv_obj_set_style_line_color(scale_, scaleColor, LV_PART_ITEMS);
        lv_obj_set_style_line_width(scale_, 2, LV_PART_INDICATOR);
        lv_obj_set_style_line_width(scale_, 1, LV_PART_ITEMS);
        lv_obj_set_style_line_opa(scale_, LV_OPA_80, LV_PART_INDICATOR);
        lv_obj_set_style_line_opa(scale_, LV_OPA_40, LV_PART_ITEMS);

        uint8_t scaleFont = config_.fontSize > 0 ? static_cast<uint8_t>(config_.fontSize - 1) : config_.fontSize;
        lv_obj_set_style_text_color(scale_, scaleColor, LV_PART_INDICATOR);
        lv_obj_set_style_text_opa(scale_, LV_OPA_80, LV_PART_INDICATOR);
        lv_obj_set_style_text_font(scale_, getFontBySize(scaleFont), LV_PART_INDICATOR);
        lv_obj_set_style_transform_rotation(scale_, LV_SCALE_LABEL_ROTATE_MATCH_TICKS | LV_SCALE_LABEL_ROTATE_KEEP_UPRIGHT,
                                            LV_PART_INDICATOR);
        int16_t labelPad = static_cast<int16_t>(-(arcWidth / 2 + 4) + scaleOffset);
        lv_obj_set_style_pad_radial(scale_, labelPad, LV_PART_INDICATOR);
    }

    if (valueLabel_) {
        Color valueCol = themeColor(config_.arcValueColor);
        lv_color_t valueColor = lv_color_make(valueCol.r, valueCol.g, valueCol.b);
        lv_obj_set_style_text_color(valueLabel_, valueColor, 0);
        lv_obj_set_style_text_font(valueLabel_, getFontBySize(config_.arcValueFontSize), 0);
        lv_obj_center(valueLabel_);
        updateValueLabel(lastValue_);
    }
}

void ArcWidget::onKnxValue(float value) {
    if (!obj_) return;

    switch (config_.textSource) {
        case TextSource::KNX_DPT_TEMP:
        case TextSource::KNX_DPT_PERCENT:
        case TextSource::KNX_DPT_POWER:
        case TextSource::KNX_DPT_ENERGY:
        case TextSource::KNX_DPT_DECIMALFACTOR:
            lastValue_ = value;
            lv_arc_set_value(obj_, clampToRange(value, config_.arcMin, config_.arcMax));
            updateValueLabel(value);
            break;
        default:
            break;
    }
}

void ArcWidget::onKnxSwitch(bool value) {
    if (!obj_) return;
    if (config_.textSource != TextSource::KNX_DPT_SWITCH) return;

    int32_t minValue = config_.arcMin;
    int32_t maxValue = config_.arcMax;
    if (maxValue <= minValue) {
        minValue = 0;
        maxValue = 100;
    }
    lastValue_ = value ? static_cast<float>(maxValue) : static_cast<float>(minValue);
    lv_arc_set_value(obj_, value ? maxValue : minValue);
    updateValueLabel(lastValue_);
}

const char* ArcWidget::unitSuffix(uint8_t unit) {
    switch (unit) {
        case 1: return "%";
        case 2: return "C";
        default: return "";
    }
}

void ArcWidget::updateValueLabel(float value) {
    if (!valueLabel_) return;
    if (!config_.arcShowValue) {
        lv_obj_add_flag(valueLabel_, LV_OBJ_FLAG_HIDDEN);
        return;
    }
    lv_obj_clear_flag(valueLabel_, LV_OBJ_FLAG_HIDDEN);

    int32_t minValue = config_.arcMin;
    int32_t maxValue = config_.arcMax;
    if (maxValue <= minValue) {
        minValue = 0;
        maxValue = 100;
    }

    float clamped = value;
    if (clamped < minValue) clamped = static_cast<float>(minValue);
    if (clamped > maxValue) clamped = static_cast<float>(maxValue);

    char buf[24];
    const char* suffix = unitSuffix(config_.arcUnit);
    bool useDecimal = (config_.textSource == TextSource::KNX_DPT_TEMP);
    if (useDecimal) {
        std::snprintf(buf, sizeof(buf), "%.1f%s", clamped, suffix);
    } else {
        std::snprintf(buf, sizeof(buf), "%.0f%s", clamped, suffix);
    }
    lv_label_set_text(valueLabel_, buf);
}
