// Copyright (C) 2023 Tasuku Suzuki <tasuku.suzuki@signal-slot.co.jp>
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0

#include "qtdesignwidgets.h"

#include "hostosinfo.h"
#include "icon.h"
#include "networkaccessmanager.h"
#include "stylehelper.h"
#include "utilsicons.h"

#include <QtTaskTree/QNetworkReplyWrapper>
#include <QtTaskTree/QTaskTree>

#include <QCache>
#include <QCommonStyle>
#include <QEvent>
#include <QGuiApplication>
#include <QLayout>
#include <QNetworkReply>
#include <QPaintEvent>
#include <QPainter>
#include <QPainterPath>
#include <QPixmapCache>
#include <QStyleOption>
#include <QVariantAnimation>
#include <QWidget>

using namespace QtTaskTree;

namespace Utils {

using namespace StyleHelper;
using namespace StyleHelper::SpacingTokens;

const qreal disabledIconOpacity = 0.3;
constexpr TextFormat TabBarTf
    {Theme::Token_Text_Muted, StyleHelper::UiElementH5};
constexpr TextFormat TabBarTfActive
    {Theme::Token_Text_Default, TabBarTf.uiElement};
constexpr TextFormat TabBarTfDisabled
    {Theme::Token_Text_Subtle, TabBarTf.uiElement};

enum WidgetState {
    WidgetStateDefault,
    WidgetStateChecked,
    WidgetStateHovered,
    WidgetStateDisabled,
};

namespace {
class QtDesignSystemStyle : public QCommonStyle
{
public:
    void drawControl(ControlElement element, const QStyleOption *opt, QPainter *painter,
                     const QWidget *widget = nullptr) const override;
    void drawPrimitive(PrimitiveElement element, const QStyleOption *opt, QPainter *painter,
                       const QWidget *widget = nullptr) const override;
    int pixelMetric(PixelMetric metric, const QStyleOption *opt = nullptr,
                    const QWidget *widget = nullptr) const override;
    QIcon standardIcon(StandardPixmap sp, const QStyleOption *opt,
                       const QWidget *widget) const override;
    void polish(QWidget *widget) override;

    static QStyle *instance();
};
} // anonymous namespace

static const TextFormat &buttonTF(QtcButton::Role role, WidgetState state)
{
    static const TextFormat largePrimaryTF {
        .themeColor = Theme::Token_Text_On_Accent,
        .uiElement = StyleHelper::UiElement::UiElementH5,
        .drawTextFlags = Qt::AlignCenter | Qt::TextDontClip | Qt::TextShowMnemonic,
    };
    static const TextFormat largeSecondaryTF {
        .themeColor = Theme::Token_Text_Default,
        .uiElement = largePrimaryTF.uiElement,
        .drawTextFlags = largePrimaryTF.drawTextFlags,
    };
    static const TextFormat mediumPrimaryTF {
        .themeColor = largePrimaryTF.themeColor,
        .uiElement = StyleHelper::UiElement::UiElementButtonMedium,
        .drawTextFlags = largePrimaryTF.drawTextFlags,
    };
    static const TextFormat mediumSecondaryTF {
        .themeColor = largeSecondaryTF.themeColor,
        .uiElement = mediumPrimaryTF.uiElement,
        .drawTextFlags = mediumPrimaryTF.drawTextFlags,
    };
    static const TextFormat smallPrimaryTF {
        .themeColor = largePrimaryTF.themeColor,
        .uiElement = StyleHelper::UiElement::UiElementButtonSmall,
        .drawTextFlags = largePrimaryTF.drawTextFlags,
    };
    static const TextFormat smallSecondaryTF {
        .themeColor = largeSecondaryTF.themeColor,
        .uiElement = smallPrimaryTF.uiElement,
        .drawTextFlags = smallPrimaryTF.drawTextFlags,
    };
    static const TextFormat smallListDefaultTF {
        .themeColor = Theme::Token_Text_Default,
        .uiElement = StyleHelper::UiElement::UiElementIconStandard,
        .drawTextFlags = Qt::AlignLeft | Qt::AlignVCenter | Qt::TextDontClip | Qt::TextShowMnemonic,
    };
    static const TextFormat smallListCheckedTF = smallListDefaultTF;
    static const TextFormat smallLinkDefaultTF {
        .themeColor = Theme::Token_Text_Default,
        .uiElement = StyleHelper::UiElement::UiElementIconStandard,
        .drawTextFlags = smallListDefaultTF.drawTextFlags,
    };
    static const TextFormat smallLinkHoveredTF {
        .themeColor = Theme::Token_Text_Accent,
        .uiElement = smallLinkDefaultTF.uiElement,
        .drawTextFlags = smallLinkDefaultTF.drawTextFlags,
    };
    static const TextFormat tagDefaultTF {
        .themeColor = Theme::Token_Text_Muted,
        .uiElement = StyleHelper::UiElement::UiElementLabelMedium,
    };
    static const TextFormat tagHoverTF {
        .themeColor = Theme::Token_Text_Default,
        .uiElement = tagDefaultTF.uiElement,
    };

    switch (role) {
    case QtcButton::LargePrimary:
        return largePrimaryTF;
    case QtcButton::LargeSecondary:
    case QtcButton::LargeTertiary:
    case QtcButton::LargeGhost:
        return largeSecondaryTF;

    case QtcButton::MediumPrimary:
        return mediumPrimaryTF;
    case QtcButton::MediumSecondary:
    case QtcButton::MediumTertiary:
    case QtcButton::MediumGhost:
        return mediumSecondaryTF;

    case QtcButton::SmallPrimary:
        return smallPrimaryTF;
    case QtcButton::SmallSecondary:
    case QtcButton::SmallTertiary:
    case QtcButton::SmallGhost:
        return smallSecondaryTF;

    case QtcButton::SmallList: return (state == WidgetStateDefault) ? smallListDefaultTF
                                             : smallListCheckedTF;
    case QtcButton::SmallLink: return (state == WidgetStateDefault) ? smallLinkDefaultTF
                                             : smallLinkHoveredTF;
    case QtcButton::Tag: return (state == WidgetStateDefault) ? tagDefaultTF
                                             : tagHoverTF;
    }
    return mediumPrimaryTF;
}

QtcButton::QtcButton(const QString &text, Role role, QWidget *parent)
    : QAbstractButton(parent)
    , m_role(role)
{
    setText(text);
    setAttribute(Qt::WA_Hover);
    setAttribute(Qt::WA_LayoutUsesWidgetRect);

    updateMargins();
    if (m_role == SmallList)
        setCheckable(true);
    else if (m_role == SmallLink)
        setCursor(Qt::PointingHandCursor);
}

QSize QtcButton::minimumSizeHint() const
{
    int maxTextWidth = 0;
    for (WidgetState state : {WidgetStateDefault, WidgetStateChecked, WidgetStateHovered} ) {
        const TextFormat &tf = buttonTF(m_role, state);
        const QFontMetrics fm(tf.font());
        const QSize textS = fm.size(Qt::TextShowMnemonic, text());
        maxTextWidth = qMax(maxTextWidth, textS.width());
    }
    const TextFormat &tf = buttonTF(m_role, WidgetStateDefault);
    const QMargins margins = contentsMargins();
    return {margins.left() + maxTextWidth + margins.right(),
            margins.top() + tf.lineHeight() + margins.bottom()};
}

static int iconLabelGap(QtcButton::Role role)
{
    switch (role) {
    case QtcButton::LargePrimary:
    case QtcButton::LargeSecondary:
    case QtcButton::LargeTertiary:
    case QtcButton::LargeGhost:
        return GapHM;
    case QtcButton::MediumPrimary:
    case QtcButton::MediumSecondary:
    case QtcButton::MediumTertiary:
    case QtcButton::MediumGhost:
    case QtcButton::SmallList:
    case QtcButton::SmallLink:
        return GapHXs;
    case QtcButton::SmallPrimary:
    case QtcButton::SmallSecondary:
    case QtcButton::SmallTertiary:
    case QtcButton::SmallGhost:
    case QtcButton::Tag:
        return GapHXs;
    }
    Q_UNREACHABLE_RETURN(-1);
}


static void paintCommonBackground(QPainter *p, const QRectF &rect, const QWidget *w)
{
    const bool enabled = w->isEnabled();
    const bool hover = w->underMouse();
    const QBrush fill(creatorColor(hover && enabled ? Theme::Token_Foreground_Subtle
                                                    : Theme::Token_Background_Muted));
    const Theme::Color c =
        enabled ? (w->hasFocus() ? Theme::Token_Stroke_Strong
                                 : (hover ? Theme::Token_Stroke_Muted
                                          : Theme::Token_Stroke_Subtle))
                : Theme::Token_Foreground_Subtle;
    const QPen pen(creatorColor(c));
    StyleHelper::drawCardBg(p, rect, fill, pen);
}

void QtcButton::paintEvent(QPaintEvent *event)
{
    // Without pixmap
    // +------------------------+-----------+------------------------+
    // |                        |(PaddingVM)|                        |
    // |                        +-----------+                        |
    // |([PaddingHXL|PaddingHM])|  <label>  |([PaddingHXL|PaddingHM])|
    // |                        +-----------+                        |
    // |                        |(PaddingVM)|                        |
    // +------------------------+-----------+------------------------+
    //
    // With pixmap
    // +-----------+--------+----------------+-----------+------------------------+
    // |           |        |                |(PaddingVM)|                        |
    // |           |        |                +-----------+                        |
    // |(PaddingHM)|<pixmap>|([GapHS|GapHXs])|  <label>  |([PaddingHXL|PaddingHM])|
    // |           |        |                +-----------+                        |
    // |           |        |                |(PaddingVM)|                        |
    // +-----------+--------+----------------+-----------+------------------------+
    //
    // SmallLink with pixmap
    // +--------+-------+-----------+------------------------+
    // |        |       |(PaddingVM)|                        |
    // |        |       +-----------+                        |
    // |<pixmap>|(GapHS)|  <label>  |([PaddingHXl|PaddingHM])|
    // |        |       +-----------+                        |
    // |        |       |(PaddingVM)|                        |
    // +--------+-------+-----------+------------------------+

    Q_UNUSED(event)
    const bool hovered = underMouse();
    const WidgetState state = isChecked() ? WidgetStateChecked : hovered ? WidgetStateHovered
                                                                         : WidgetStateDefault;
    const TextFormat &tf = buttonTF(m_role, state);
    const QMargins margins = contentsMargins();
    const QRect bgR = rect();

    QPainter p(this);

    switch (m_role) {
    case LargePrimary:
    case MediumPrimary:
    case SmallPrimary: {
        const Theme::Color color = isEnabled() ? (isDown()
                                                  ? Theme::Token_Accent_Subtle
                                                  : (hovered ? Theme::Token_Accent_Muted
                                                             : Theme::Token_Accent_Default))
                                               : Theme::Token_Foreground_Subtle;
        const QBrush fill(creatorColor(color));
        StyleHelper::drawCardBg(&p, bgR, fill);
        break;
    }
    case LargeSecondary:
    case MediumSecondary:
    case SmallSecondary: {
        const Theme::Color outlineColor = isEnabled() ? Theme::Token_Stroke_Strong
                                                      : Theme::Token_Stroke_Subtle;
        const qreal width = hovered ? 2.0 : 1.0;
        const QPen outline(creatorColor(outlineColor), width);
        const QBrush fillColor = isDown() ? creatorColor(Theme::Token_Foreground_Subtle)
                                          : QBrush(Qt::NoBrush);
        StyleHelper::drawCardBg(&p, bgR, fillColor, outline);
        break;
    }
    case LargeGhost:
    case MediumGhost:
    case SmallGhost:
        if (!hovered && !isDown())
            break;
        [[fallthrough]];
    case LargeTertiary:
    case MediumTertiary:
    case SmallTertiary: {
        const Theme::Color bg = isEnabled() ? (isDown() ? Theme::Token_Foreground_Default
                                                        : (hovered ? Theme::Token_Foreground_Muted
                                                                   : Theme::Token_Foreground_Subtle))
                                            : Theme::Token_Foreground_Subtle;
        StyleHelper::drawCardBg(&p, bgR, creatorColor(bg));
        break;
    }
    case SmallList: {
        const bool filled = hovered || isDown() || isChecked();
        if (filled) {
            const QBrush fill(creatorColor(isChecked() ? Theme::Token_Foreground_Muted
                                                       : Theme::Token_Foreground_Subtle));
            StyleHelper::drawCardBg(&p, bgR, fill);
        }
        break;
    }
    case SmallLink:
        break;
    case Tag: {
        const bool filled = hovered || isDown() || isChecked();
        const QBrush fill(filled ? creatorColor(Theme::Token_Foreground_Subtle)
                                 : QBrush(Qt::NoBrush));
        const QPen outline(hovered ? QPen(Qt::NoPen) : creatorColor(Theme::Token_Stroke_Subtle));
        StyleHelper::drawCardBg(&p, bgR, fill, outline);
        break;
    }
    }

    if (!m_pixmap.isNull()) {
        const QSizeF pixmapS = m_pixmap.deviceIndependentSize();
        const int pixmapX = text().isEmpty()
                                ? (bgR.width() - pixmapS.width()) / 2
                                : margins.left() - iconLabelGap(m_role) - pixmapS.width();
        const int pixmapY = (bgR.height() - pixmapS.height()) / 2;
        p.drawPixmap(pixmapX, pixmapY, m_pixmap);
    }

    const int availableLabelWidth = bgR.width() - margins.left() - margins.right();
    const QFont font = tf.font();
    const QFontMetrics fm(font);
    const QString elidedLabelText = fm.elidedText(text(), Qt::ElideRight, availableLabelWidth);
    const QRect labelR(margins.left(), margins.top(), availableLabelWidth, tf.lineHeight());
    p.setFont(font);
    const QColor textColor = isEnabled() ? tf.color() : creatorColor(Theme::Token_Text_Subtle);
    p.setPen(textColor);
    p.drawText(labelR, tf.drawTextFlags, elidedLabelText);
}

void QtcButton::setPixmap(const QPixmap &pixmap)
{
    m_pixmap = pixmap;
    updateMargins();
    update();
}

void QtcButton::setRole(Role role)
{
    m_role = role;
    updateMargins();
}

void QtcButton::updateMargins()
{
    int hPaddingR = -1;
    int vPadding = -1;

    switch (m_role) {
    case MediumPrimary:
    case MediumSecondary:
    case MediumTertiary:
    case MediumGhost:
    case SmallList:
    case SmallLink:
        hPaddingR = PaddingHL;
        vPadding = PaddingVM;
        break;
    case LargePrimary:
    case LargeSecondary:
    case LargeTertiary:
    case LargeGhost:
        hPaddingR = PaddingHXl;
        vPadding = PaddingVL;
        break;
    case SmallPrimary:
    case SmallSecondary:
    case SmallTertiary:
    case SmallGhost:
        hPaddingR = PaddingHM;
        vPadding = PaddingVS;
        break;
    case Tag:
        hPaddingR = PaddingHM;
        vPadding = PaddingVXs;
        break;
    }

    const int hPaddingL = m_pixmap.isNull() ? hPaddingR
                                            : hPaddingR
                                                  + int(m_pixmap.deviceIndependentSize().width())
                                                  + iconLabelGap(m_role);

    setContentsMargins(hPaddingL, vPadding, hPaddingR, vPadding);
}

QtcLabel::QtcLabel(const QString &text, Role role, QWidget *parent)
    : QLabel(text, parent)
{
    setRole(role);
}

void QtcLabel::setRole(Role role)
{
    static const TextFormat primaryTF{Theme::Token_Text_Muted, StyleHelper::UiElement::UiElementH3};
    static const TextFormat
        secondaryTF{primaryTF.themeColor, StyleHelper::UiElement::UiElementH6Capital};

    const TextFormat &tF = role == Primary ? primaryTF : secondaryTF;
    const int vPadding = role == Primary ? PaddingVS : PaddingVM;

    setFixedHeight(vPadding + tF.lineHeight() + vPadding);
    setFont(tF.font());
    QPalette pal = palette();
    pal.setColor(QPalette::Active, QPalette::WindowText, tF.color());
    pal.setColor(QPalette::Disabled, QPalette::WindowText, creatorColor(Theme::Token_Text_Subtle));
    setPalette(pal);

    update();
}

constexpr TextFormat lineEditTextTF
    {Theme::Token_Text_Default, StyleHelper::UiElement::UiElementBody2};
constexpr TextFormat lineEditPlaceholderTF
    {Theme::Token_Text_Muted, lineEditTextTF.uiElement};

QtcLineEdit::QtcLineEdit(QWidget *parent)
    : FancyLineEdit(parent)
{
    setAttribute(Qt::WA_MacShowFocusRect, false);
    setAutoFillBackground(false);
    setFont(lineEditTextTF.font());
    setFrame(false);
    setMouseTracking(true);

    QPalette pal = palette();
    pal.setColor(QPalette::Base, Qt::transparent);
    pal.setColor(QPalette::Active, QPalette::PlaceholderText, lineEditPlaceholderTF.color());
    pal.setColor(QPalette::Disabled, QPalette::PlaceholderText,
                 creatorColor(Theme::Token_Text_Subtle));
    pal.setColor(QPalette::Text, lineEditTextTF.color());
    pal.setColor(QPalette::Disabled, QPalette::Text,
                 creatorColor(Theme::Token_Text_Subtle));
    setPalette(pal);

    setContentsMargins({PaddingHM, PaddingVS, 0, PaddingVS});
    setFixedHeight(PaddingVS + lineEditTextTF.lineHeight() + PaddingVS);
}

QSize QtcLineEdit::minimumSizeHint() const
{
    const QFontMetrics fm(lineEditTextTF.font());
    const QSize textS = fm.size(Qt::TextSingleLine, text());
    const QMargins margins = contentsMargins();
    return {margins.left() + textS.width() + margins.right(),
            margins.top() + lineEditTextTF.lineHeight() + margins.bottom()};
}

void QtcLineEdit::enterEvent(QEnterEvent *event)
{
    QLineEdit::enterEvent(event);
    update();
}

void QtcLineEdit::leaveEvent(QEvent *event)
{
    QLineEdit::leaveEvent(event);
    update();
}

void QtcLineEdit::paintEvent(QPaintEvent *event)
{
    // +-----------+-----------+-----------+
    // |           |(PaddingVS)|           |
    // |           +-----------+           |
    // |(PaddingHM)| <lineEdit>|(PaddingHM)|
    // |           +-----------+           |
    // |           |(PaddingVS)|           |
    // +-----------+-----------+-----------+

    QPainter p(this);
    paintCommonBackground(&p, rect(), this);
    QLineEdit::paintEvent(event);
}

static const QPixmap &searchBoxIcon()
{
    static const QPixmap icon = Icon({{FilePath::fromString(":/core/images/search.png"),
                                       Theme::Token_Text_Muted}}, Icon::Tint).pixmap();
    return icon;
}

QtcSearchBox::QtcSearchBox(QWidget *parent)
    : QtcLineEdit(parent)
{
    setFiltering(true);
}

void QtcSearchBox::paintEvent(QPaintEvent *event)
{
    QtcLineEdit::paintEvent(event);

    if (text().isEmpty()) {
        QPainter p(this);
        const QPixmap icon = searchBoxIcon();
        const QSize iconS = icon.deviceIndependentSize().toSize();
        const QPoint iconPos(width() - PaddingHM - iconS.width(), (height() - iconS.height()) / 2);
        if (!isEnabled())
            p.setOpacity(disabledIconOpacity);
        p.drawPixmap(iconPos, icon);
    }
}

constexpr TextFormat comboBoxTf {
    .themeColor = Theme::Token_Text_Default, // All states except disabled
    .uiElement = StyleHelper::UiElementLabelMedium,
    .drawTextFlags = Qt::AlignLeft | Qt::AlignVCenter | Qt::TextDontClip
};

static const QPixmap &comboBoxIcon(WidgetState state)
{
    const FilePath mask = FilePath::fromString(":/core/images/expandarrow.png");
    switch (state) {
    case WidgetStateDefault: {
        static const QPixmap icon = Icon({{mask, Theme::Token_Text_Muted}}, Icon::Tint).pixmap();
        return icon;
    }
    case WidgetStateChecked:
    case WidgetStateHovered: {
        static const QPixmap icon = Icon({{mask, Theme::Token_Text_Default}}, Icon::Tint).pixmap();
        return icon;
    }
    case WidgetStateDisabled:
    default: {
        static const QPixmap icon = Icon({{mask, Theme::Token_Text_Subtle}}, Icon::Tint).pixmap();
        return icon;
    }
    }
}

QtcComboBox::QtcComboBox(Role role, QWidget *parent)
    : QComboBox(parent)
{
    setAttribute(Qt::WA_LayoutUsesWidgetRect);
    setFont(comboBoxTf.font());
    setMouseTracking(true);
    setRole(role);
}

QtcComboBox::QtcComboBox(QWidget *parent)
    : QtcComboBox(LargePrimary, parent)
{}

void QtcComboBox::setRole(Role role)
{
    m_role = role;
    setFont(comboBoxTf.font());
    const QSize iconSize = comboBoxIcon(WidgetStateDefault).deviceIndependentSize().toSize();
    const int hPadding = m_role == SmallPrimary ? PaddingHXs : PaddingHM;
    const int vPadding = m_role == SmallPrimary ? PaddingVXs : PaddingVM;
    const int hGap = m_role == SmallPrimary ? GapHXs : GapHM;
    setContentsMargins({hPadding, vPadding, hGap + iconSize.width() + hPadding, vPadding});
    update();
}

QSize QtcComboBox::sizeHint() const
{
    const QSize parentS = QComboBox::sizeHint();
    const QMargins margins = contentsMargins();
    return {margins.left() + parentS.width() + margins.right(),
            margins.top() + comboBoxTf.lineHeight() + margins.bottom()};
}

QSize QtcComboBox::minimumSizeHint() const
{
    const QSize parentS = QComboBox::minimumSizeHint();
    return {parentS.width(), sizeHint().height()};
}

void QtcComboBox::enterEvent(QEnterEvent *event)
{
    QComboBox::enterEvent(event);
    update();
}

void QtcComboBox::leaveEvent(QEvent *event)
{
    QComboBox::leaveEvent(event);
    update();
}

void QtcComboBox::paintEvent(QPaintEvent *)
{
    // Small|Default:
    // +----------------------+----------------------+--------------+-------+----------------------+
    // |                      |(PaddingVXs|PaddingVM)|              |       |                      |
    // |                      +----------------------+              |       |                      |
    // |(PaddingHXs|PaddingHM)|    <currentItem>     |(GapHXs|GapHM)|<arrow>|(PaddingHXs|PaddingHM)|
    // |                      +----------------------+              |       |                      |
    // |                      |(PaddingVXs|PaddingVM)|              |       |                      |
    // +----------------------+----------------------+--------------+-------+----------------------+

    QPainter p(this);
    paintCommonBackground(&p, rect(), this);

    const QMargins margins = contentsMargins();
    const QRect textR(margins.left(), margins.top(),
                      width() - margins.right(), comboBoxTf.lineHeight());
    p.setFont(comboBoxTf.font());
    const QColor color = isEnabled() ? comboBoxTf.color() : creatorColor(Theme::Token_Text_Subtle);
    p.setPen(color);
    p.drawText(textR, comboBoxTf.drawTextFlags, currentText());

    const WidgetState state = hasFocus() ? WidgetStateChecked
                                         : (underMouse() ? WidgetStateHovered
                                                         : (isEnabled() ? WidgetStateDefault
                                                                        : WidgetStateDisabled));

    const QPixmap icon = comboBoxIcon(state);
    const QSize iconS = icon.deviceIndependentSize().toSize();
    const QPoint iconPos(width() - contentsMargins().left() - iconS.width(),
                         (height() - iconS.height()) / 2);
    p.drawPixmap(iconPos, icon);
}

constexpr TextFormat SwitchLabelTf
    {Theme::Token_Text_Default, StyleHelper::UiElementLabelMedium};
constexpr QSize switchTrackS(32, 16);

QtcSwitch::QtcSwitch(const QString &text, QWidget *parent)
    : QAbstractButton(parent)
{
    setText(text);
    setCheckable(true);
    setAttribute(Qt::WA_Hover);
    setAttribute(Qt::WA_LayoutUsesWidgetRect);
    setLayoutDirection(Qt::RightToLeft); // Switch right, label left
}

QSize QtcSwitch::sizeHint() const
{
    const QFontMetrics fm(SwitchLabelTf.font());
    const int textWidth = fm.size(Qt::TextShowMnemonic, text()).width();
    const int width = switchTrackS.width() + GapHM + textWidth;
    return {width, PaddingVS + SwitchLabelTf.lineHeight() + PaddingVS};
}

QSize QtcSwitch::minimumSizeHint() const
{
    return switchTrackS;
}

void QtcSwitch::paintEvent([[maybe_unused]] QPaintEvent *event)
{
    const bool ltr = layoutDirection() == Qt::LeftToRight;
    const int trackX = ltr ? 0 : width() - switchTrackS.width();
    const int trackY = (height() - switchTrackS.height()) / 2;
    const QRect trackR(QPoint(trackX, trackY), switchTrackS);
    const int trackRounding = trackR.height() / 2;
    const bool checkedEnabled = isChecked() && isEnabled();
    QPainter p(this);

    { // track
        const bool hovered = underMouse();
        const QBrush fill = creatorColor(checkedEnabled ? (hovered ? Theme::Token_Accent_Subtle
                                                                   : Theme::Token_Accent_Default)
                                                        : Theme::Token_Foreground_Subtle);
        const QPen outline = checkedEnabled ? QPen(Qt::NoPen)
                                            : creatorColor(hovered ? Theme::Token_Stroke_Muted
                                                                   : Theme::Token_Stroke_Subtle);
        StyleHelper::drawCardBg(&p, trackR, fill, outline, trackRounding);
    }
    { // track label
        const QColor color = creatorColor(isEnabled() ? (isChecked() ? Theme::Token_Text_On_Accent
                                                                     : Theme::Token_Text_Muted)
                                                      : Theme::Token_Text_Subtle);
        const int labelS = 6;
        const int labelY = (height() - labelS) / 2;
        if (isChecked()) {
            const QRect onLabelR(trackX + 8, labelY, 1, labelS);
            p.fillRect(onLabelR, color);
        } else {
            const QRect offLabelR(trackX + switchTrackS.width() - trackRounding - labelS / 2 - 1,
                                  labelY, labelS, labelS);
            StyleHelper::drawCardBg(&p, offLabelR, Qt::NoBrush, color, labelS / 2);
        }
    }
    { // knob
        const int thumbPadding = checkedEnabled ? 3 : 2;
        const int thumbH = switchTrackS.height() - thumbPadding - thumbPadding;
        const int thumbW = isDown() ? (checkedEnabled ? 17 : 19)
                                    : thumbH;
        const int thumbRounding = thumbH / 2;
        const int thumbX = trackX + (isChecked() ? switchTrackS.width() - thumbW - thumbPadding
                                                 : thumbPadding);
        const QRect thumbR(thumbX, trackY + thumbPadding, thumbW, thumbH);
        const QBrush fill = creatorColor(isEnabled() ? Theme::Token_Basic_White
                                                     : Theme::Token_Foreground_Default);
        const QPen outline = checkedEnabled ? QPen(Qt::NoPen)
                                            : creatorColor(Theme::Token_Stroke_Subtle);
        StyleHelper::drawCardBg(&p, thumbR, fill, outline, thumbRounding);
    }
    { // switch text label
        const int switchAndGapWidth = switchTrackS.width() + GapHM;
        const QRect textR(ltr ? switchAndGapWidth : 0, 0, width() - switchAndGapWidth,
                          trackY + switchTrackS.height());
        p.setFont(SwitchLabelTf.font());
        p.setPen(isEnabled() ? SwitchLabelTf.color() : creatorColor(Theme::Token_Text_Subtle));
        const QString elidedLabel =
            p.fontMetrics().elidedText(text(), Qt::ElideRight, textR.width());
        p.drawText(textR, SwitchLabelTf.drawTextFlags, elidedLabel);
    }
}

constexpr int progressBarTrackHeight = PrimitiveM;
constexpr int progressBarMinimumWidth = 64;

QtcProgressBar::QtcProgressBar(QWidget *parent)
    : QProgressBar(parent)
{
    setAttribute(Qt::WA_LayoutUsesWidgetRect);
    setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
    setTextVisible(false);
}

QSize QtcProgressBar::sizeHint() const
{
    return {progressBarMinimumWidth, progressBarTrackHeight};
}

QSize QtcProgressBar::minimumSizeHint() const
{
    return {progressBarTrackHeight, progressBarTrackHeight};
}

void QtcProgressBar::paintEvent([[maybe_unused]] QPaintEvent *event)
{
    const int trackY = (height() - progressBarTrackHeight) / 2;
    const QRect trackR(0, trackY, width(), progressBarTrackHeight);
    const int rounding = progressBarTrackHeight / 2;

    QPainter p(this);

    const QBrush trackFill = creatorColor(Theme::Token_Foreground_Subtle);
    StyleHelper::drawCardBg(&p, trackR, trackFill, QPen(Qt::NoPen), rounding);

    const int span = maximum() - minimum();
    if (span > 0 && value() > minimum()) {
        const int fillWidth = qRound(qreal(value() - minimum()) / span * trackR.width());
        if (fillWidth > 0) {
            const QRect fillR(trackR.x(), trackR.y(), fillWidth, trackR.height());
            const QBrush fill = creatorColor(isEnabled() ? Theme::Token_Accent_Default
                                                         : Theme::Token_Foreground_Default);
            StyleHelper::drawCardBg(&p, fillR, fill, QPen(Qt::NoPen), rounding);
        }
    }
}

QtcIconButton::QtcIconButton(QWidget *parent)
    : QAbstractButton(parent)
{
    setAttribute(Qt::WA_Hover);
    setAttribute(Qt::WA_LayoutUsesWidgetRect);
}

void QtcIconButton::paintEvent(QPaintEvent *e)
{
    Q_UNUSED(e)

    QPainter p(this);
    QRect r(QPoint(), size());

    if (underMouse() && isEnabled()) {
        QColor c = creatorColor(Theme::TextColorDisabled);
        c.setAlphaF(c.alphaF() * .5);
        StyleHelper::drawPanelBgRect(&p, r, c);
    }

    if (!isEnabled())
        p.setOpacity(disabledIconOpacity);
    icon().paint(&p, r, Qt::AlignCenter);
}

QSize QtcIconButton::sizeHint() const
{
    QSize s = icon().actualSize(QSize(32, 16)) + QSize(8, 8);

    if (StyleHelper::toolbarStyle() == StyleHelper::ToolbarStyle::Relaxed)
        s += QSize(5, 5);

    return s;
}

QtcRectangleWidget::QtcRectangleWidget(QWidget *parent)
    : QWidget(parent)
{
    setContentsMargins(m_radius, m_radius, m_radius, m_radius);
}

void QtcRectangleWidget::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event);
    QPainter painter(this);
    Utils::StyleHelper::drawCardBg(&painter, rect(), m_fillBrush, m_strokePen, m_radius);
}

int QtcRectangleWidget::radius() const
{
    return m_radius;
}

void QtcRectangleWidget::setRadius(int radius)
{
    if (m_radius != radius) {
        m_radius = radius;
        setContentsMargins(m_radius, m_radius, m_radius, m_radius);
        update();
    }
}

void QtcRectangleWidget::setStrokePen(QPen pen)
{
    m_strokePen = pen;
    update();
}

QPen QtcRectangleWidget::strokePen() const
{
    return m_strokePen;
}

void QtcRectangleWidget::setFillBrush(const QBrush &brush)
{
    m_fillBrush = brush;
    update();
}

QBrush QtcRectangleWidget::fillBrush() const
{
    return m_fillBrush;
}

class CachedImage : public QObject
{
    Q_OBJECT
public:
    CachedImage(const QUrl &url)
    {
        // We have to make our own key here, cause other places might change the image before storing
        // it in the cache.
        const auto pixmapCacheKey = url.toString() + "CachedImage";
        if (url.isLocalFile()) {
            const QString filePath = url.toLocalFile();
            if (QPixmapCache::find(pixmapCacheKey, &px))
                return;
            if (px.load(filePath)) {
                QPixmapCache::insert(pixmapCacheKey, px);
            }
        } else {
            const auto onSetup = [url](QNetworkReplyWrapper &task) {
                task.setNetworkAccessManager(Utils::NetworkAccessManager::instance());
                task.setRequest(QNetworkRequest(url));
            };

            const auto onDone = [this, pixmapCacheKey](const QNetworkReplyWrapper &task,
                                                       DoneWith result) {
                if (result == DoneWith::Success) {
                    const QByteArray data = task.reply()->readAll();
                    if (px.loadFromData(data)) {
                        QPixmapCache::insert(pixmapCacheKey, px);
                        emit imageReady();
                        return;
                    }
                }
                emit imageError();
            };

            taskTree.setRecipe({ QNetworkReplyWrapperTask(onSetup, onDone) });
            taskTree.start();
        }
    }
    QPixmap pixmap() const { return px; }

signals:
    void imageReady();
    void imageError();

private:
    QPixmap px;
    QTaskTree taskTree;
};

class ImageCache
{
public:
    static ImageCache *instance()
    {
        static ImageCache instance;
        return &instance;
    }

    CachedImage *get(const QUrl &url)
    {
        CachedImage *cachedImage = m_cache.object(url);
        if (!cachedImage) {
            cachedImage = new CachedImage(url);
            bool ok = m_cache.insert(url, cachedImage);
            QTC_ASSERT(ok, return nullptr);
        }
        return cachedImage;
    }

private:
    QCache<QUrl, CachedImage> m_cache;
};

QtcImage::QtcImage(QWidget *parent)
    : QWidget(parent)
{
    QSizePolicy sizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
    sizePolicy.setHeightForWidth(true);
    setSizePolicy(sizePolicy);
}

void QtcImage::setUrl(const QString &url)
{
    update();
    updateGeometry();

    m_pixmap = QPixmap();
    if (m_cachedImage)
        m_cachedImage->disconnect(this);

    m_cachedImage = ImageCache::instance()->get(QUrl(url));
    QTC_ASSERT(m_cachedImage, return);

    if (m_cachedImage->pixmap().isNull()) {
        connect(m_cachedImage, &CachedImage::imageReady, this, [this]() {
            setPixmap(m_cachedImage->pixmap());
            update();
            updateGeometry();
        });
        connect(m_cachedImage, &CachedImage::imageError, this, [this]() {
            update();
            updateGeometry();
        });
        return;
    }

    setPixmap(m_cachedImage->pixmap());
    m_cachedImage = nullptr;
}

void QtcImage::setRadius(int radius)
{
    if (m_radius == radius)
        return;
    m_radius = radius;
    update();
}

int QtcImage::radius() const
{
    return m_radius;
}

void QtcImage::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event)
    QPainter p(this);
    p.setRenderHint(QPainter::Antialiasing);
    p.setRenderHint(QPainter::SmoothPixmapTransform);

    if (m_pixmap.isNull())
        return;

    const QRect rect = contentsRect();

    if (m_radius > 0) {
        const qreal radius = m_radius;
        QPainterPath path;
        path.addRoundedRect(rect, radius, radius);
        p.setClipPath(path);
    }

    p.drawPixmap(rect, m_pixmap);
}

QSize QtcImage::sizeForWidth(int width) const
{
    if (m_pixmap.size().width() == 0)
        return QSize(width, width);

    const QSize orgImageSize = m_pixmap.deviceIndependentSize().toSize();

    if (orgImageSize.width() == 0)
        return QSize(width, width);

    const auto margins = this->contentsMargins();
    const QSize contentsMargin(margins.left() + margins.right(), margins.top() + margins.bottom());

    const QSize contentsSize(width, orgImageSize.height() * width / orgImageSize.width());
    QSize hint
        = QSize(contentsSize + contentsMargin).expandedTo(minimumSize()).boundedTo(maximumSize());
    hint.setHeight(m_pixmap.size().height() * hint.width() / m_pixmap.size().width());
    return hint;
}

QSize QtcImage::sizeHint() const
{
    return sizeForWidth(m_pixmap.deviceIndependentSize().width());
}

int QtcImage::heightForWidth(int width) const
{
    if (this->layout())
        return QWidget::heightForWidth(width);

    if (m_pixmap.isNull())
        return 0;
    return sizeForWidth(width).height();
}

void QtcImage::setPixmap(const QPixmap &px)
{
    m_pixmap = px;
    update();
}

QtcTabBar::QtcTabBar(QWidget *parent)
    : QTabBar(parent)
{
    setStyle(QtDesignSystemStyle::instance());
    setMouseTracking(true);
}

bool QtcTabBar::event(QEvent *event)
{
    if (event->type() == QEvent::MouseMove || event->type() == QEvent::Leave)
        repaint();
    return QTabBar::event(event);
}

const int pageIndicatorDotSize = PrimitiveM;
const int pageIndicatorDotGap = PrimitiveS; // Gap between two dots

QtcPageIndicator::QtcPageIndicator(QWidget *parent)
    : QWidget(parent)
{
    m_animation = new QVariantAnimation(this);
    m_animation->setDuration(400);
    m_animation->setStartValue(0);
    m_animation->setEndValue(100);
    m_animation->setEasingCurve(QEasingCurve::OutQuad);
}

QSize QtcPageIndicator::minimumSizeHint() const
{
    const int dotsWidth = m_pagesCount * pageIndicatorDotSize
                          + (m_pagesCount - 1) * pageIndicatorDotGap;
    return {dotsWidth, pageIndicatorDotSize};
}

void QtcPageIndicator::setPagesCount(int count)
{
    m_pagesCount = count;
    setCurrentPage(m_currentPage);
}

int QtcPageIndicator::pagesCount() const
{
    return m_pagesCount;
}

void QtcPageIndicator::setCurrentPage(int current)
{
    if (m_pagesCount <= 0)
        return;
    m_previousPage = qBound(0, m_currentPage, m_pagesCount - 1);
    m_currentPage = qBound(0, current, m_pagesCount - 1);
    m_animation->stop();
    m_animation->start();
    update();
}

int QtcPageIndicator::currentPage() const
{
    return m_currentPage;
}

void QtcPageIndicator::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event);
    const QColor activeDot = creatorColor(isEnabled() ? Theme::Token_Accent_Default
                                                      : Theme::Token_Foreground_Default);
    const QColor inactiveDot = creatorColor(isEnabled() ? Theme::Token_Foreground_Default
                                                        : Theme::Token_Foreground_Subtle);
    const QColor previousDot = StyleHelper::mergedColors(inactiveDot, activeDot,
                                                         m_animation->currentValue().toInt());
    QPainter p(this);
    p.setPen(Qt::NoPen);
    p.setRenderHint(QPainter::Antialiasing);
    for (int i = 0; i < m_pagesCount; ++i) {
        const int x = i * (pageIndicatorDotSize + pageIndicatorDotGap);
        const QRectF dotRect(x, 0, pageIndicatorDotSize, pageIndicatorDotSize);
        const QBrush fill = i == m_currentPage ? activeDot
                                               : (i == m_previousPage ? previousDot : inactiveDot);
        p.setBrush(fill);
        p.drawEllipse(dotRect);
    }
    if (m_animation->state() == QAbstractAnimation::Running)
        update();
}


class IconDisplayPrivate
{
public:
    QIcon m_icon;
};

QtcIconDisplay::QtcIconDisplay(QWidget *parent)
    : QWidget(parent)
    , d(std::make_unique<IconDisplayPrivate>())
{
    setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
}

QtcIconDisplay::~QtcIconDisplay() = default;

void QtcIconDisplay::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event)

    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);
    painter.setRenderHint(QPainter::SmoothPixmapTransform);

    qreal dpr = devicePixelRatio();
    QSize actualSize = size();
    auto px = d->m_icon.pixmap(actualSize, dpr);
    px.setDevicePixelRatio(dpr);

    QRect r(QPoint(0, 0), actualSize);
    r.moveCenter(rect().center());

    painter.drawPixmap(r, px);
}

QSize QtcIconDisplay::sizeHint() const
{
    if (d->m_icon.isNull())
        return {};

    if (auto sizes = d->m_icon.availableSizes(); !sizes.isEmpty())
        return sizes.first();

    return {};
}

void QtcIconDisplay::setIcon(const Icon &icon)
{
    d->m_icon = icon.icon();
    update();
}

constexpr StyleHelper::TextFormat badgeTfPrimary {
    .themeColor = Theme::Token_Basic_White,
    .uiElement = StyleHelper::UiElementLabelSmall,
    .drawTextFlags = Qt::AlignCenter,
};

constexpr StyleHelper::TextFormat badgeTfSecondary {
    .themeColor = Theme::Token_Text_Default,
    .uiElement = badgeTfPrimary.uiElement,
    .drawTextFlags = badgeTfPrimary.drawTextFlags,
};

static Theme::Color colorForInfoType(InfoLabel::InfoType infoType)
{
    switch (infoType) {
    case Utils::InfoLabel::Information:
        return Theme::Token_Notification_Neutral_Muted;
    case Utils::InfoLabel::Warning:
        return Theme::Token_Notification_Alert_Muted;
    case Utils::InfoLabel::Error:
    case Utils::InfoLabel::NotOk:
        return Theme::Token_Notification_Danger_Muted;
    case Utils::InfoLabel::Ok:
    case Utils::InfoLabel::None:
    default:
        return Theme::Token_Notification_Success_Muted;
    }
}

QtcBadge::QtcBadge(QWidget *parent)
    : QWidget(parent)
{
}

QSize QtcBadge::minimumSizeHint() const
{
    const int height = StyleHelper::SpacingTokens::PaddingVXxs
                       + badgeTfPrimary.lineHeight()
                       + StyleHelper::SpacingTokens::PaddingVXxs;
    const QFontMetrics fm(badgeTfPrimary.font());
    const int textWidth = QFontMetrics(badgeTfPrimary.font()).boundingRect(text()).width();
    const int width = StyleHelper::SpacingTokens::PaddingHXs
                      + textWidth
                      + StyleHelper::SpacingTokens::PaddingHXs;
    return {width, height};
}

QString QtcBadge::text() const
{
    return m_text;
}

void QtcBadge::setText(const QString &text)
{
    m_text = text;
    update();
}

InfoLabel::InfoType QtcBadge::infoType() const
{
    return m_infoType;
}

void QtcBadge::setInfoType(InfoLabel::InfoType newInfoType)
{
    m_infoType = newInfoType;
    update();
}

QtcBadge::Role QtcBadge::role() const
{
    return m_role;
}

void QtcBadge::setRole(Role role)
{
    m_role = role;
    update();
}

void QtcBadge::paintEvent(QPaintEvent *event)
{
    QWidget::paintEvent(event);

    const QColor color = creatorColor(isEnabled() ? colorForInfoType(m_infoType)
                                                  : Theme::Token_Foreground_Subtle);
    const bool filled = m_role == NumberPrimary;
    const QBrush fill = filled ? QBrush(color) : Qt::NoBrush;
    const QPen pen = filled ? Qt::NoPen : QPen(color);
    const qreal rounding = qMin(height(), width()) / 2.0;
    QPainter p(this);
    StyleHelper::drawCardBg(&p, rect(), fill,  pen, rounding);
    const TextFormat &tf = filled ? badgeTfPrimary : badgeTfSecondary;
    const QColor fontColor = isEnabled() ? tf.color() : creatorColor(Theme::Token_Text_Subtle);
    p.setPen(fontColor);
    p.setFont(tf.font());
    p.drawText(rect(), tf.drawTextFlags, m_text);
}

namespace QtDesignWidgets {

Button::Button()
{
    ptr = new Implementation({}, QtcButton::MediumPrimary, nullptr);
}

Button::Button(std::initializer_list<I> ps)
    : Utils::QtDesignWidgets::Button()
{
    Layouting::Tools::apply(this, ps);
}

void Button::setText(const QString &text)
{
    Layouting::Tools::access(this)->setText(text);
}

void Button::setIcon(const Icon &icon)
{
    Layouting::Tools::access(this)->setPixmap(icon.pixmap());
}

void Button::setRole(QtcButton::Role role)
{
    Layouting::Tools::access(this)->setRole(role);
}

void Button::onClicked(QObject *guard, const std::function<void()> &func)
{
    QObject::connect(Layouting::Tools::access(this), &QtcButton::clicked, guard, func);
}

IconButton::IconButton()
{
    ptr = new Implementation(nullptr);
}

IconButton::IconButton(std::initializer_list<I> ps)
    : IconButton()
{
    Layouting::Tools::apply(this, ps);
}

void IconButton::setIcon(const Icon &icon)
{
    Layouting::Tools::access(this)->setIcon(icon.icon());
}

void IconButton::onClicked(QObject *guard, const std::function<void()> &func)
{
    QObject::connect(Layouting::Tools::access(this), &QtcIconButton::clicked, guard, func);
}

Switch::Switch()
{
    ptr = new Implementation({});
}

Switch::Switch(std::initializer_list<I> ps)
    : Switch()
{
    Layouting::Tools::apply(this, ps);
}

void Switch::setText(const QString &text)
{
    Layouting::Tools::access(this)->setText(text);
}

void Switch::setChecked(bool checked)
{
    Layouting::Tools::access(this)->setChecked(checked);
}

void Switch::onClicked(QObject *guard, const std::function<void()> &func)
{
    QObject::connect(Layouting::Tools::access(this), &QtcSwitch::clicked, guard, func);
}

ProgressBar::ProgressBar()
{
    ptr = new Implementation();
}

ProgressBar::ProgressBar(std::initializer_list<I> ps)
    : ProgressBar()
{
    Layouting::Tools::apply(this, ps);
}

void ProgressBar::setMinimum(int minimum)
{
    Layouting::Tools::access(this)->setMinimum(minimum);
}

void ProgressBar::setMaximum(int maximum)
{
    Layouting::Tools::access(this)->setMaximum(maximum);
}

void ProgressBar::setRange(int minimum, int maximum)
{
    Layouting::Tools::access(this)->setRange(minimum, maximum);
}

void ProgressBar::setValue(int value)
{
    Layouting::Tools::access(this)->setValue(value);
}

void ProgressBar::onValueChanged(QObject *guard, const std::function<void(int)> &func)
{
    QObject::connect(Layouting::Tools::access(this), &QtcProgressBar::valueChanged, guard, func);
}

Label::Label()
{
    ptr = new Implementation("", QtcLabel::Primary, nullptr);
}

Label::Label(std::initializer_list<I> ps)
    : Label()
{
    Layouting::Tools::apply(this, ps);
}

void Label::setText(const QString &text)
{
    Layouting::Tools::access(this)->setText(text);
}

void Label::setRole(QtcLabel::Role role)
{
    Layouting::Tools::access(this)->setRole(role);
}

LineEdit::LineEdit()
{
    ptr = new Implementation();
}

LineEdit::LineEdit(std::initializer_list<I> ps)
    : LineEdit()
{
    Layouting::Tools::apply(this, ps);
}

void LineEdit::setPlaceholderText(const QString &text)
{
    Layouting::Tools::access(this)->setPlaceholderText(text);
}

void LineEdit::setText(const QString &text)
{
    Layouting::Tools::access(this)->setText(text);
}

void LineEdit::onTextChanged(QObject *guard, const std::function<void(QString)> &func)
{
    QObject::connect(Layouting::Tools::access(this), &QtcLineEdit::textChanged, guard, func);
}

SearchBox::SearchBox()
{
    ptr = new Implementation();
}

SearchBox::SearchBox(std::initializer_list<I> ps)
    : SearchBox()
{
    Layouting::Tools::apply(this, ps);
}

void SearchBox::setPlaceholderText(const QString &text)
{
    Layouting::Tools::access(this)->setPlaceholderText(text);
}

void SearchBox::setText(const QString &text)
{
    Layouting::Tools::access(this)->setText(text);
}

void SearchBox::onTextChanged(QObject *guard, const std::function<void(QString)> &func)
{
    QObject::connect(Layouting::Tools::access(this), &QtcSearchBox::textChanged, guard, func);
}

Rectangle::Rectangle(std::initializer_list<I> ps)
{
    ptr = new Implementation;
    Layouting::Tools::apply(this, ps);
}

void Rectangle::setFillBrush(const QBrush &brush)
{
    Layouting::Tools::access(this)->setFillBrush(brush);
}

void Rectangle::setStrokePen(const QPen &pen)
{
    Layouting::Tools::access(this)->setStrokePen(pen);
}

void Rectangle::setRadius(int radius)
{
    Layouting::Tools::access(this)->setRadius(radius);
}

Image::Image(std::initializer_list<I> ps)
{
    ptr = new Implementation;
    Layouting::Tools::apply(this, ps);
}

void Image::setUrl(const QString &url)
{
    Layouting::Tools::access(this)->setUrl(url);
}

void Image::setRadius(int radius)
{
    Layouting::Tools::access(this)->setRadius(radius);
}

IconDisplay::IconDisplay(std::initializer_list<I> ps)
{
    ptr = new Implementation;
    Layouting::Tools::apply(this, ps);
}

void IconDisplay::setIcon(const Utils::Icon &icon)
{
    Layouting::Tools::access(this)->setIcon(icon);
}

Badge::Badge()
{
    ptr = new Implementation();
}

Badge::Badge(std::initializer_list<I> ps)
    : Badge()
{
    Layouting::Tools::apply(this, ps);
}

void Badge::setText(const QString &text)
{
    Layouting::Tools::access(this)->setText(text);
}

void Badge::setInfoType(InfoLabel::InfoType infoType)
{
    Layouting::Tools::access(this)->setInfoType(infoType);
}

void Badge::setRole(QtcBadge::Role role)
{
    Layouting::Tools::access(this)->setRole(role);
}

} // namespace QtDesignWidgets

void QtDesignSystemStyle::drawControl(ControlElement element, const QStyleOption *opt,
                                      QPainter *painter, const QWidget *widget) const
{
    switch (element) {
    case CE_TabBarTabLabel: {
        auto tabOpt = qstyleoption_cast<const QStyleOptionTab*>(opt);
        QStyleOptionTab myTabOpt = *tabOpt;
        const QColor c = ((myTabOpt.state & QStyle::State_Enabled) ?
                              (myTabOpt.state & QStyle::State_Selected) ? TabBarTfActive
                                                                        : TabBarTf
                                                                   : TabBarTfDisabled).color();
        myTabOpt.palette.setColor(widget->foregroundRole(), c);
        myTabOpt.state = myTabOpt.state & ~QStyle::State_HasFocus; // Avoid extra focus rect
        QCommonStyle::drawControl(element, &myTabOpt, painter, widget);
        break;
    }
    case CE_TabBarTabShape: {
        const bool selected = opt->state & QStyle::State_Selected;
        const bool enabled = opt->state & QStyle::State_Enabled;
        const bool hovered = !selected && opt->rect.contains(widget->mapFromGlobal(QCursor::pos()));
        auto tabOpt = qstyleoption_cast<const QStyleOptionTab*>(opt);
        const int paddingL = tabOpt->position == QStyleOptionTab::Beginning ? 0 : PaddingHXxs;
        const int paddingR = tabOpt->position == QStyleOptionTab::End ? 0 : PaddingHXxs;
        const QRect shapeR = opt->rect.adjusted(paddingL, 0, -paddingR, 0);
        if (selected || (hovered && enabled)) {
            const bool isMoving = tabOpt->position == QStyleOptionTab::Moving;
            if (hovered || isMoving) {
                StyleHelper::drawCardBg(painter, shapeR.adjusted(0, 0, 0, +SpacingTokens::RadiusS),
                                        creatorColor(Theme::Token_Foreground_Subtle));
            }
            QRect highLightRect = shapeR;
            highLightRect.moveTop(highLightRect.height() - StyleHelper::HighlightThickness);
            const QColor color = creatorColor(enabled ? hovered ? Theme::Token_Text_Subtle
                                                                : Theme::Token_Accent_Default
                                                      : Theme::Token_Foreground_Subtle);
            painter->fillRect(highLightRect, color);
        }
        break;
    }
    default:
        QCommonStyle::drawControl(element, opt, painter, widget);
        break;
    }
}

void QtDesignSystemStyle::drawPrimitive(PrimitiveElement element, const QStyleOption *opt,
                                        QPainter *painter, const QWidget *widget) const
{
    switch (element) {
    case PE_FrameTabBarBase: {
        QRectF borderR = opt->rect.toRectF();
        const int lineWidth = 1;
        borderR.setTop(borderR.bottom() - lineWidth);
        const bool enabled = opt->state & QStyle::State_Enabled;
        const QColor color = creatorColor(enabled ? Theme::Token_Stroke_Subtle
                                                  : Theme::Token_Foreground_Subtle);
        painter->fillRect(borderR, color);
        break;
    }
    default:
        QCommonStyle::drawPrimitive(element, opt, painter, widget);
        break;
    }
}

int QtDesignSystemStyle::pixelMetric(PixelMetric m, const QStyleOption *opt,
                                     const QWidget *widget) const
{
    switch (m) {
    case PM_TabBarTabShiftVertical:
    case PM_TabBarTabShiftHorizontal:
        return 0;
    case PM_TabBarTabHSpace:
        return PaddingHL + PaddingHL;
    case PM_TabBarTabVSpace: {
        const int textHeight = opt->fontMetrics.height();
        return PaddingVM + TabBarTf.lineHeight() + PaddingVM - textHeight;
    }
    default:
        return QCommonStyle::pixelMetric(m, opt, widget);
    }
}

QIcon QtDesignSystemStyle::standardIcon(StandardPixmap sp, const QStyleOption *opt,
                                        const QWidget *widget) const
{
    QIcon icon;

    switch (sp) {
    case SP_TabCloseButton: {
        static const QIcon tabClose = Icons::CLOSE_FOREGROUND.icon();
        icon = tabClose;
        break;
    }
    default:
        icon = QCommonStyle::standardIcon(sp, opt, widget);
        break;
    }

    return icon;
}
void QtDesignSystemStyle::polish(QWidget *widget)
{
    QCommonStyle::polish(widget);

    if (auto tabBar = qobject_cast<QTabBar*>(widget)) {
        tabBar->setFont(TabBarTf.font());
        for (int count = tabBar->count(), i = 0; i < count; ++i ) {
            for (const QTabBar::ButtonPosition pos : {QTabBar::LeftSide, QTabBar::RightSide}) {
                if (QWidget *tabButton = tabBar->tabButton(i, pos))
                    tabButton->setStyle(this);
            }
        }
    }
}

QStyle *QtDesignSystemStyle::instance()
{
    static QtDesignSystemStyle style;
    return &style;
}

} // namespace Utils

#include "qtdesignwidgets.moc"
