#include "PowerLinkWidget.hpp"
#include "../WidgetManager.hpp"
#include <cmath>
#include <cstdlib>

PowerLinkWidget::PowerLinkWidget(const WidgetConfig& config)
    : Widget(config)
{
    for (uint8_t i = 0; i < POINT_COUNT; i++) {
        points_[i].x = 0;
        points_[i].y = 0;
    }
}

PowerLinkWidget::~PowerLinkWidget() {
    lv_anim_del(this, nullptr);
    if (dot_ != nullptr) {
        if (lv_obj_is_valid(dot_)) {
            lv_obj_delete(dot_);
        }
        dot_ = nullptr;
    }
}

static float parseFloatOr(const char* text, float fallback) {
    if (text == nullptr || text[0] == '\0') return fallback;
    char* end = nullptr;
    float value = std::strtof(text, &end);
    if (end == text || !std::isfinite(value)) return fallback;
    return value;
}

void PowerLinkWidget::buildLinePoints() {
    if (obj_ == nullptr) return;
    if (config_.x < 0 || config_.y < 0) return;

    const ScreenConfig* screen = WidgetManager::instance().currentScreen();
    if (screen == nullptr) return;

    const WidgetConfig* fromNode = screen->findWidget(static_cast<uint8_t>(config_.x));
    const WidgetConfig* toNode = screen->findWidget(static_cast<uint8_t>(config_.y));
    if (fromNode == nullptr || toNode == nullptr) return;
    if (fromNode->parentId != config_.parentId || toNode->parentId != config_.parentId) return;

    float x1 = static_cast<float>(fromNode->x) + static_cast<float>(fromNode->width) * 0.5f;
    float y1 = static_cast<float>(fromNode->y) + static_cast<float>(fromNode->height) * 0.5f;
    float x2 = static_cast<float>(toNode->x) + static_cast<float>(toNode->width) * 0.5f;
    float y2 = static_cast<float>(toNode->y) + static_cast<float>(toNode->height) * 0.5f;

    float dx = x2 - x1;
    float dy = y2 - y1;
    float len = std::sqrt(dx * dx + dy * dy);
    if (len < 0.001f) return;

    float lineWidth = config_.width > 0 ? static_cast<float>(config_.width) : 3.0f;
    if (lineWidth < 3.0f) lineWidth = 3.0f;
    int16_t minSideA = fromNode->width < fromNode->height ? fromNode->width : fromNode->height;
    int16_t minSideB = toNode->width < toNode->height ? toNode->width : toNode->height;
    float radiusA = static_cast<float>(minSideA) * 0.5f - lineWidth * 0.5f;
    float radiusB = static_cast<float>(minSideB) * 0.5f - lineWidth * 0.5f;
    if (radiusA < 0.0f) radiusA = 0.0f;
    if (radiusB < 0.0f) radiusB = 0.0f;

    float ux = dx / len;
    float uy = dy / len;
    float startX = x1 + ux * radiusA;
    float startY = y1 + uy * radiusA;
    float endX = x2 - ux * radiusB;
    float endY = y2 - uy * radiusB;

    if (len <= radiusA + radiusB + 1.0f) {
        startX = x1;
        startY = y1;
        endX = x2;
        endY = y2;
    }

    float dxTrim = endX - startX;
    float dyTrim = endY - startY;
    float lenTrim = std::sqrt(dxTrim * dxTrim + dyTrim * dyTrim);
    if (lenTrim < 0.001f) lenTrim = len;

    float nx = -dyTrim / lenTrim;
    float ny = dxTrim / lenTrim;
    float midX = (startX + endX) * 0.5f;
    float midY = (startY + endY) * 0.5f;
    float curve = std::fmin(40.0f, lenTrim * 0.25f);
    float curveSign = (config_.iconPosition == 2) ? -1.0f : 1.0f;
    if (config_.iconPosition == 0) {
        curveSign = (config_.id % 2 == 0) ? 1.0f : -1.0f;
    } else if (config_.iconPosition == 1) {
        curveSign = 1.0f;
    } else if (config_.iconPosition == 3) {
        curveSign = -1.0f;
    }

    p0x_ = startX;
    p0y_ = startY;
    p1x_ = midX + nx * curve * curveSign;
    p1y_ = midY + ny * curve * curveSign;
    p2x_ = endX;
    p2y_ = endY;
    pathLen_ = lenTrim;

    float minXf = p0x_;
    float minYf = p0y_;
    float maxXf = p0x_;
    float maxYf = p0y_;

    for (uint8_t i = 0; i < POINT_COUNT; i++) {
        float t = static_cast<float>(i) / static_cast<float>(POINT_COUNT - 1);
        float inv = 1.0f - t;
        float x = inv * inv * p0x_ + 2.0f * inv * t * p1x_ + t * t * p2x_;
        float y = inv * inv * p0y_ + 2.0f * inv * t * p1y_ + t * t * p2y_;
        if (x < minXf) minXf = x;
        if (y < minYf) minYf = y;
        if (x > maxXf) maxXf = x;
        if (y > maxYf) maxYf = y;
    }

    int32_t minX = static_cast<int32_t>(std::floor(minXf));
    int32_t minY = static_cast<int32_t>(std::floor(minYf));
    int32_t maxX = static_cast<int32_t>(std::ceil(maxXf));
    int32_t maxY = static_cast<int32_t>(std::ceil(maxYf));

    for (uint8_t i = 0; i < POINT_COUNT; i++) {
        float t = static_cast<float>(i) / static_cast<float>(POINT_COUNT - 1);
        float inv = 1.0f - t;
        float x = inv * inv * p0x_ + 2.0f * inv * t * p1x_ + t * t * p2x_;
        float y = inv * inv * p0y_ + 2.0f * inv * t * p1y_ + t * t * p2y_;
        points_[i].x = x - minX;
        points_[i].y = y - minY;
    }

    lv_obj_set_pos(obj_, minX, minY);
    lv_obj_set_size(obj_, (maxX - minX) + 1, (maxY - minY) + 1);
    lv_line_set_points(obj_, points_, POINT_COUNT);

    dotAnimExec(this, 0);
}

lv_obj_t* PowerLinkWidget::create(lv_obj_t* parent) {
    obj_ = lv_line_create(parent);
    if (obj_ == nullptr) {
        return nullptr;
    }

    lv_obj_clear_flag(obj_, LV_OBJ_FLAG_CLICKABLE);
    lv_obj_move_to_index(obj_, 0);
    buildLinePoints();

    dot_ = lv_obj_create(parent);
    if (dot_ != nullptr) {
        lv_obj_remove_style_all(dot_);
        lv_obj_clear_flag(dot_, LV_OBJ_FLAG_CLICKABLE);
        lv_obj_clear_flag(dot_, LV_OBJ_FLAG_SCROLLABLE);
        lv_obj_move_to_index(dot_, 1);
    }
    startDotAnimation();
    return obj_;
}

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

    uint16_t lineWidth = config_.width > 0 ? config_.width : 3;
    if (lineWidth < 3) lineWidth = 3;

    lv_obj_set_style_line_color(obj_, lv_color_make(
        config_.bgColor.r, config_.bgColor.g, config_.bgColor.b), 0);
    lv_obj_set_style_line_width(obj_, lineWidth, 0);
    lv_obj_set_style_line_opa(obj_, config_.bgOpacity, 0);
    lv_obj_set_style_line_dash_width(obj_, 0, 0);
    lv_obj_set_style_line_dash_gap(obj_, 0, 0);

    if (dot_ != nullptr) {
        uint16_t dotSize = static_cast<uint16_t>(lineWidth * 3);
        if (dotSize < 6) dotSize = 6;
        if (dotSize > 16) dotSize = 16;
        dotRadius_ = dotSize * 0.5f;

        lv_obj_set_size(dot_, dotSize, dotSize);
        lv_obj_set_style_radius(dot_, LV_RADIUS_CIRCLE, 0);
        lv_obj_set_style_bg_color(dot_, lv_color_make(
            config_.bgColor.r, config_.bgColor.g, config_.bgColor.b), 0);
        lv_obj_set_style_bg_opa(dot_, config_.bgOpacity, 0);
        dotAnimExec(this, 0);
    }
}

void PowerLinkWidget::startDotAnimation() {
    if (dot_ == nullptr || obj_ == nullptr) return;

    if (config_.textSource == TextSource::STATIC) {
        float speed = parseFloatOr(config_.text, 60.0f);
        updateAnimation(speed, false);
    } else {
        updateAnimation(0.0f, false);
    }
}

void PowerLinkWidget::dotAnimExec(void* var, int32_t value) {
    auto* self = static_cast<PowerLinkWidget*>(var);
    if (self == nullptr || self->dot_ == nullptr) return;
    float t = static_cast<float>(value) / 1000.0f;
    if (self->reverse_) {
        t = 1.0f - t;
    }
    float inv = 1.0f - t;
    float x = inv * inv * self->p0x_ + 2.0f * inv * t * self->p1x_ + t * t * self->p2x_;
    float y = inv * inv * self->p0y_ + 2.0f * inv * t * self->p1y_ + t * t * self->p2y_;
    lv_obj_set_pos(self->dot_,
        static_cast<int32_t>(x - self->dotRadius_),
        static_cast<int32_t>(y - self->dotRadius_));
}

void PowerLinkWidget::updateAnimation(float speed, bool reverse) {
    if (dot_ == nullptr || obj_ == nullptr) return;
    bool shouldHide = (speed <= 0.01f || pathLen_ <= 0.5f);
    bool isHidden = lv_obj_has_flag(dot_, LV_OBJ_FLAG_HIDDEN);
    if (std::fabs(speed - speed_) < 0.01f && reverse == reverse_ && isHidden == shouldHide) {
        return;
    }

    speed_ = speed;
    reverse_ = reverse;

    lv_anim_del(this, nullptr);

    if (shouldHide) {
        lv_obj_add_flag(dot_, LV_OBJ_FLAG_HIDDEN);
        return;
    }

    lv_obj_clear_flag(dot_, LV_OBJ_FLAG_HIDDEN);

    int32_t duration = static_cast<int32_t>((pathLen_ / speed_) * 1000.0f);
    if (duration < 800) duration = 800;
    if (duration > 12000) duration = 12000;

    lv_anim_t anim;
    lv_anim_init(&anim);
    lv_anim_set_var(&anim, this);
    lv_anim_set_exec_cb(&anim, PowerLinkWidget::dotAnimExec);
    lv_anim_set_values(&anim, 0, 1000);
    lv_anim_set_repeat_count(&anim, LV_ANIM_REPEAT_INFINITE);
    lv_anim_set_path_cb(&anim, lv_anim_path_linear);
    lv_anim_set_time(&anim, duration);
    lv_anim_start(&anim);

    dotAnimExec(this, 0);
}

void PowerLinkWidget::onKnxValue(float value) {
    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;

    float factor = parseFloatOr(config_.text, 1.0f);
    float speed = std::fabs(value) * factor;
    updateAnimation(speed, value < 0.0f);
}

void PowerLinkWidget::onKnxSwitch(bool value) {
    if (config_.textSource != TextSource::KNX_DPT_SWITCH) return;
    float factor = parseFloatOr(config_.text, 1.0f);
    float speed = value ? factor : 0.0f;
    updateAnimation(speed, false);
}
