// Copyright (c) 2014-2026 The Dash Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <qt/guiutil_font.h>

#include <tinyformat.h>
#include <util/system.h>

#include <qt/guiutil.h>

#include <QApplication>
#include <QDebug>
#include <QFontDatabase>
#include <QFontMetrics>
#include <QGuiApplication>
#include <QPointer>
#include <QScreen>
#include <QTextBlock>
#include <QTextCharFormat>
#include <QTextCursor>
#include <QWidget>

#include <cmath>
#include <map>
#include <memory>
#include <optional>
#include <utility>

namespace {
//! Instance of font database shared among calls
std::unique_ptr<QFontDatabase> g_font_db{nullptr};

//! loadFonts stores the SystemDefault font in g_default_font to be able to reference it later again
std::unique_ptr<QFont> g_default_font{nullptr};

//! Font scaling information for Qt classes. The base size is captured on the first pass;
//! std::nullopt means "not captured yet", which a plain -1 could not express because that
//! is also what a pixel-sized font reports as its point size.
std::map<std::string, std::optional<double>> mapClassFontUpdates{
    {"QMenu", std::nullopt},
    {"QMessageBox", std::nullopt},
    {"QTipLabel", std::nullopt},
};

//! Contains all widgets and its font attributes (weight, italic, size) with font changes due to GUIUtil::setFont
std::map<QPointer<QWidget>, GUIUtil::FontAttrib> mapFontUpdates;

//! Contains QTextEdit widgets with the original base font size and HTML
struct TextEditStyleData {
    QString html;
    double base_size;
};
std::map<QPointer<QTextEdit>, TextEditStyleData> mapTextEditStyleUpdates;

//! Map between font weights, Montserrat's convention and italic availability
const std::map<QFont::Weight, std::pair<std::string, /*can_italic=*/bool>> mapMontserrat{{
    {QFont::Black, {"Black", true}},
    {QFont::Bold, {"Bold", true}},
    {QFont::DemiBold, {"SemiBold", true}},
    {QFont::ExtraBold, {"ExtraBold", true}},
    {QFont::ExtraLight, {"ExtraLight", true}},
    {QFont::Light, {"Light", true}},
    {QFont::Medium, {"Medium", true}},
    {QFont::Normal, {"Regular", false}},
    {QFont::Thin, {"Thin", true}},
}};

//! Map between font weights and settings representation
const auto mapWeightArgs = []() {
    std::map<int, QFont::Weight> kv{
        {0, QFont::Thin},
        {1, QFont::ExtraLight},
        {2, QFont::Light},
        {3, QFont::Normal},
        {4, QFont::Medium},
        {5, QFont::DemiBold},
        {6, QFont::Bold},
        {7, QFont::ExtraBold},
        {8, QFont::Black}
    };
    std::map<QFont::Weight, int> vk;
    for (const auto& [key, val] : kv) {
        vk[val] = key;
    }
    return std::pair{std::move(kv), std::move(vk)};
}();

//! List of Qt classes to ignore when applying fonts
constexpr std::array<std::string_view, 18> vecIgnoreClasses{
    "BitcoinGUI",
    "QComboBoxPrivateContainer",
    "QComboBoxPrivateScroller",
    "QDesktopScreenWidget",
    "QDesktopWidget",
    "QDialog",
    "QFrame",
    "QGroupBox",
    "QListView",
    "QMenu",
    "QMessageBox",
    "QScrollBar",
    "QStackedWidget",
    "QTipLabel",
    "QVBoxLayout",
    "QWidget",
    "WalletFrame",
    "WalletView",
};

//! List of Qt objects to ignore when applying fonts
constexpr std::array<std::string_view, 1> vecIgnoreObjects{
    "messagesWidget",
};

//! Weights considered when testing for weights supported by a font
const auto vecWeightConsider = []() {
    std::vector<QFont::Weight> ret;
    for (const auto& [key, _] : mapWeightArgs.second) {
        ret.push_back(key);
    }
    return ret;
}();

//! Wrapper for tinyformat (strprintf) that converts to QString
template <typename... Args>
QString qstrprintf(const std::string& fmt, const Args&... args)
{
    return QString::fromStdString(tfm::format(fmt, args...));
}

//! Returns a properly weighted QFont object with the selected font
QFont getFont(const GUIUtil::FontAttrib& font_attrib)
{
    QFont font;
    if (!GUIUtil::fontsLoaded()) {
        return font;
    }

    // Resolve weight from FontWeight type
    const QFont::Weight weight = (font_attrib.m_weight_type == GUIUtil::FontWeight::Bold)
                               ? GUIUtil::g_font_registry.GetWeightBold() : GUIUtil::g_font_registry.GetWeightNormal();

    if (font_attrib.m_font == GUIUtil::MONTSERRAT_FONT_STR) {
        assert(mapMontserrat.count(weight));
#ifdef Q_OS_MACOS
        font.setFamily(font_attrib.m_font);
        font.setStyleName([&]() {
            std::string ret{mapMontserrat.at(weight).first};
            if (font_attrib.m_is_italic) {
                if (ret == "Regular") {
                    ret = "Italic";
                } else {
                    ret += " Italic";
                }
            }
            return QString::fromStdString(ret);
        }());
#else
        if (weight == QFont::Normal || weight == QFont::Bold) {
            font.setFamily(font_attrib.m_font);
        } else {
            font.setFamily(qstrprintf("%s %s", font_attrib.m_font.toStdString(), mapMontserrat.at(weight).first));
        }
#endif // Q_OS_MACOS
    } else if (font_attrib.m_font == GUIUtil::OS_FONT_STR) {
        font.setFamily(g_default_font->family());
    } else if (font_attrib.m_font == GUIUtil::OS_MONO_FONT_STR) {
        font.setFamily(QFontDatabase::systemFont(QFontDatabase::FixedFont).family());
    } else {
        font.setFamily(font_attrib.m_font);
    }

    if (font_attrib.m_font == GUIUtil::ROBOTO_MONO_FONT_STR || font_attrib.m_font == GUIUtil::OS_MONO_FONT_STR) {
        font.setStyleHint(QFont::Monospace);
    }

#ifdef Q_OS_MACOS
    if (font_attrib.m_font != GUIUtil::MONTSERRAT_FONT_STR)
#endif // Q_OS_MACOS
    {
        font.setWeight(weight);
        font.setStyle(font_attrib.m_is_italic ? QFont::StyleItalic : QFont::StyleNormal);
    }

    if (font_attrib.m_point_size != -1) {
        font.setPointSizeF(GUIUtil::g_font_registry.GetScaledFontSize(font_attrib.m_point_size));
    }

    if (gArgs.GetBoolArg("-debug-ui", false)) {
        qDebug() << qstrprintf("%s: font size: %d, family: %s, style: %s, weight: %d match %s", __func__,
                               font.pointSizeF(), font.family().toStdString(), font.styleName().toStdString(),
                               font.weight(), font.exactMatch() ? "true" : "false");
    }

    return font;
}

//! Removes entries from a map where QPointer keys point to deleted objects
template <typename T>
size_t pruneStaleEntities(T& map)
{
    size_t removed{0};
    auto it = map.begin();
    while (it != map.end()) {
        if (it->first.isNull()) {
            it = map.erase(it);
            ++removed;
        } else {
            ++it;
        }
    }
    return removed;
}

//! Initializes QTextEdit with HTML and applies font styling
void setFontBodyHTML(QTextEdit* widget, const QString& src, double base_size)
{
    if (!GUIUtil::fontsLoaded() || !widget) return;

    QFont font{GUIUtil::getScaledFont(base_size, /*bold=*/false)};
    widget->setFont(font);
    widget->setHtml(src);

    QFont font_bold{GUIUtil::getScaledFont(base_size, /*bold=*/true)};
    QTextDocument* doc{widget->document()};
    for (QTextBlock block{doc->begin()}; block.isValid(); block = block.next()) {
        const int heading_level{block.blockFormat().headingLevel()};
        double scale_add{0};
        switch (heading_level) {
        case 1:
            scale_add = 0.10;
            break;
        case 2:
            scale_add = 0.04;
            break;
        case 3:
            scale_add = 0.02;
            break;
        default:
            break;
        }
        for (auto it = block.begin(); !it.atEnd(); ++it) {
            QTextFragment fragment{it.fragment()};
            if (!fragment.isValid()) {
                continue;
            }
            if (fragment.charFormat().fontWeight() >= QFont::Bold || scale_add > 0) {
                QTextCursor cursor(doc);
                cursor.setPosition(fragment.position());
                cursor.setPosition(fragment.position() + fragment.length(), QTextCursor::KeepAnchor);

                QTextCharFormat fmt;
                fmt.setFontFamily(font_bold.family());
                fmt.setFontWeight(font_bold.weight());
#ifdef Q_OS_MACOS
                if (!font_bold.styleName().isEmpty()) {
                    fmt.setFontStyleName(font_bold.styleName());
                }
#endif // Q_OS_MACOS
                if (scale_add > 0) {
                    fmt.setFontPointSize(GUIUtil::g_font_registry.GetScaledFontSize(base_size * (1 + scale_add)));
                }
                cursor.mergeCharFormat(fmt);
            }
        }
    }
}
} // anonymous namespace

namespace GUIUtil {
//! Fonts known by the client
std::vector<std::pair<QString, /*selectable=*/bool>> g_fonts_known{
    {MONTSERRAT_FONT_STR.toUtf8(), true},
    {OS_FONT_STR.toUtf8(), true},
    {OS_MONO_FONT_STR.toUtf8(), false},
    {ROBOTO_MONO_FONT_STR.toUtf8(), false},
};

FontRegistry g_font_registry;

FontInfo::FontInfo(const QString& font_name)
{
    CalcSupportedWeights(font_name);
    CalcDefaultWeights(font_name);
    m_bold = m_bold_default;
    m_normal = m_normal_default;
}

FontInfo::~FontInfo() = default;

bool FontRegistry::RegisterFont(const QString& font, bool selectable, bool skip_checks)
{
    const auto font_strs{getFonts(/*selectable_only=*/false)};
    auto font_it{std::find(font_strs.begin(), font_strs.end(), font)};
    if (m_weights.count(font)) {
        // Font's already registered
        assert(font_it != font_strs.end());
        // Overwrite selectable flag
        g_fonts_known.at(std::distance(font_strs.begin(), font_it)).second = selectable;
        return true;
    }
    if (!skip_checks) {
        if (!g_font_db) { g_font_db = std::make_unique<QFontDatabase>(); }
        if (!g_font_db->families().contains(font, Qt::CaseInsensitive)) {
            // Font doesn't exist
            return false;
        }
    }
    m_weights.emplace(font, FontInfo(font));
    if (font_it == font_strs.end()) {
        g_fonts_known.emplace_back(font, selectable);
    }
    return true;
}

bool FontRegistry::SetFont(const QString& font)
{
    if (!m_weights.count(font)) {
        return false;
    }
    m_font = font;
    return true;
}

bool weightFromArg(int nArg, QFont::Weight& weight)
{
    auto it = mapWeightArgs.first.find(nArg);
    if (it == mapWeightArgs.first.end()) {
        return false;
    }
    weight = it->second;
    return true;
}

int weightToArg(const QFont::Weight weight)
{
    assert(mapWeightArgs.second.count(weight));
    return mapWeightArgs.second.find(weight)->second;
}

//! Internal helper to create a font with explicit weight (used for font detection)
static QFont getFontWithWeight(const QString& font_name, QFont::Weight weight, double point_size)
{
    QFont font;
    if (font_name == MONTSERRAT_FONT_STR) {
        if (mapMontserrat.count(weight)) {
#ifdef Q_OS_MACOS
            font.setFamily(font_name);
            font.setStyleName(QString::fromStdString(mapMontserrat.at(weight).first));
#else
            if (weight == QFont::Normal || weight == QFont::Bold) {
                font.setFamily(font_name);
            } else {
                font.setFamily(qstrprintf("%s %s", font_name.toStdString(), mapMontserrat.at(weight).first));
            }
#endif
        }
    } else if (font_name == OS_FONT_STR && g_default_font) {
        font.setFamily(g_default_font->family());
    } else {
        font.setFamily(font_name);
    }
#ifdef Q_OS_MACOS
    if (font_name != MONTSERRAT_FONT_STR)
#endif
    {
        font.setWeight(weight);
    }
    font.setPointSizeF(point_size);
    return font;
}

void FontInfo::CalcSupportedWeights(const QString& font_name)
{
    auto getTestWidth = [](const QString& font_name, QFont::Weight weight) -> int {
        QFont font = getFontWithWeight(font_name, weight, FontRegistry::DEFAULT_FONT_SIZE);
        return TextWidth(QFontMetrics(font), ("Check the width of this text to see if the weight change has an impact!"));
    };
    QFont::Weight prevWeight = vecWeightConsider.front();
    bool isFirst = true;
    for (const auto& weight : vecWeightConsider) {
        if (isFirst) {
            isFirst = false;
            continue;
        }
        if (getTestWidth(font_name, prevWeight) != getTestWidth(font_name, weight)) {
            if (m_supported_weights.empty()) {
                m_supported_weights.push_back(prevWeight);
            }
            m_supported_weights.push_back(weight);
        }
        prevWeight = weight;
    }
    if (m_supported_weights.empty()) {
        m_supported_weights.push_back(QFont::Normal);
    }
}

QFont::Weight FontInfo::GetBestMatch(const QString& font_name, QFont::Weight target)
{
    assert(!m_supported_weights.empty());

    QFont::Weight bestWeight = m_supported_weights.front();
    int nBestDiff = abs(bestWeight - target);
    for (const auto& weight : m_supported_weights) {
        int nDiff = abs(weight - target);
        if (nDiff < nBestDiff) {
            bestWeight = weight;
            nBestDiff = nDiff;
        }
    }
    return bestWeight;
}

void FontInfo::CalcDefaultWeights(const QString& font_name)
{
    assert(!m_supported_weights.empty());

    m_normal_default = GetBestMatch(font_name, FontRegistry::TARGET_WEIGHT_NORMAL);
    m_bold_default = GetBestMatch(font_name, FontRegistry::TARGET_WEIGHT_BOLD);
    if (m_normal_default == m_bold_default) {
        // If the results are the same use the next possible weight for bold font
        auto it = std::find(m_supported_weights.begin(), m_supported_weights.end(), m_normal_default);
        if (++it != m_supported_weights.end()) {
            m_bold_default = *it;
        }
    }
}

FontAttrib::FontAttrib(QString font, FontWeight weight_type, double point_size, bool is_italic) :
    m_font{font},
    m_weight_type{weight_type},
    m_point_size{point_size},
    m_is_italic{is_italic}
{
}

FontAttrib::FontAttrib(FontWeight weight_type, double point_size, bool is_italic) :
    m_font{g_font_registry.GetFont()},
    m_weight_type{weight_type},
    m_point_size{point_size},
    m_is_italic{is_italic}
{
}

FontAttrib::~FontAttrib() = default;

namespace internal {
std::optional<double> effectivePointSize(const QFont& font, int dpi_y)
{
    // Both accessors return a non-positive sentinel when the size was given in the other
    // unit, and a font with no usable size at all reports non-positive from both. Compare
    // against 0 rather than -1: the exact sentinel is not guaranteed (a box-engine fallback
    // yields values such as -0.72).
    if (const double point_size{font.pointSizeF()}; point_size > 0) {
        return point_size;
    }
    if (const int pixel_size{font.pixelSize()}; pixel_size > 0 && dpi_y > 0) {
        // Mirrors Qt's own pixel-to-point conversion in QFontDatabase::load(), including
        // its guard against a non-positive DPI.
        return pixel_size * 72.0 / dpi_y;
    }
    return std::nullopt;
}
} // namespace internal

bool loadFonts()
{
    // Before any font changes store the applications default font to use it as SystemDefault.
    g_default_font = std::make_unique<QFont>(QApplication::font());

    std::vector<int> vecFontIds{};
    auto importFont = [&vecFontIds](const QString& font_name) -> void {
        vecFontIds.push_back(QFontDatabase::addApplicationFont(font_name));
        qDebug() << qstrprintf("%s: %s loaded with id %d", __func__, font_name.toStdString(), vecFontIds.back());
    };

    // Import the italic Montserrat variant as it doesn't map to a weight
    importFont(qstrprintf(":fonts/%s-Italic", MONTSERRAT_FONT_STR.toUtf8().toStdString()));
    // Import the rest of Montserrat variants
    for (const auto& [_, val] : mapMontserrat) {
        const auto& [variant, can_italic] = val;
        importFont(qstrprintf(":fonts/%s-%s", MONTSERRAT_FONT_STR.toUtf8().toStdString(), variant));
        if (can_italic) {
            importFont(qstrprintf(":fonts/%s-%sItalic", MONTSERRAT_FONT_STR.toUtf8().toStdString(), variant));
        }
    }

    // Fail if an added id is -1 which means QFontDatabase::addApplicationFont failed.
    if (std::find(vecFontIds.begin(), vecFontIds.end(), -1) != vecFontIds.end()) {
        g_default_font = nullptr;
        return false;
    }

#ifndef QT_NO_DEBUG
    if (!g_font_db) { g_font_db = std::make_unique<QFontDatabase>(); }

    // Print debug logs for added fonts fetched by the added ids
    for (const auto& i : vecFontIds) {
        for (const QString& f : QFontDatabase::applicationFontFamilies(i)) {
            qDebug() << qstrprintf("%s: - Font id %d is family: %s", __func__, i, f.toStdString());
            for (const QString& style : g_font_db->styles(f)) {
                qDebug() << qstrprintf("%s: Style for family %s with id: %d is %s", __func__, f.toStdString(), i,
                                       style.toStdString());
            }
        }
    }

    // Print debug logs for added fonts fetched by the family name
    for (const QString& f : g_font_db->families()) {
        if (f.contains(MONTSERRAT_FONT_STR)) {
            for (const QString& style : g_font_db->styles(f)) {
                qDebug() << qstrprintf("%s: Family: %s, Style: %s", __func__, f.toStdString(), style.toStdString());
            }
        }
    }
#endif // QT_NO_DEBUG

    for (const auto& [fonts, selectable] : g_fonts_known) {
        assert(g_font_registry.RegisterFont(fonts, selectable, /*skip_checks=*/true));
    }

    return true;
}

bool fontsLoaded()
{
    return g_default_font != nullptr;
}

void setApplicationFont()
{
    if (!fontsLoaded()) {
        return;
    }

    std::unique_ptr<QFont> font;

    auto family = g_font_registry.GetFont();
    if (family == MONTSERRAT_FONT_STR) {
#ifdef Q_OS_MACOS
        font = std::make_unique<QFont>(getFontNormal());
#else
        font = std::make_unique<QFont>(family);
        font->setWeight(g_font_registry.GetWeightNormal());
#endif
    } else if (family == OS_FONT_STR) {
        font = std::make_unique<QFont>(*g_default_font);
    } else {
        font = std::make_unique<QFont>(family);
    }

    font->setPointSizeF(g_font_registry.GetFontSize());
    qApp->setFont(*font);

    qDebug() << qstrprintf("%s: %s family: %s, style: %s match: %s", __func__, qApp->font().toString().toStdString(),
                           qApp->font().family().toStdString(), qApp->font().styleName().toStdString(),
                           qApp->font().exactMatch() ? "true" : "false");
}

void setFont(const std::vector<QWidget*>& vecWidgets, const FontAttrib& font_attrib)
{
    for (auto it : vecWidgets) {
        auto itFontUpdate = mapFontUpdates.emplace(std::make_pair(it, font_attrib));
        if (!itFontUpdate.second) {
            itFontUpdate.first->second = font_attrib;
        }
    }
}

void updateFonts()
{
    // Fonts need to be loaded by GUIUtil::loadFonts(), if not just return.
    if (!g_default_font) {
        return;
    }

    static std::map<QPointer<QWidget>, double> mapWidgetDefaultFontSizes;

    // QPointer becomes nullptr for objects that were deleted.
    // Remove them from mapDefaultFontSize and mapFontUpdates
    // before proceeding any further.
    const size_t nRemovedDefaultFonts{pruneStaleEntities(mapWidgetDefaultFontSizes)};
    const size_t nRemovedFontUpdates{pruneStaleEntities(mapFontUpdates)};
    const size_t nRemovedTextEditUpdates{pruneStaleEntities(mapTextEditStyleUpdates)};

    size_t nUpdatable{0}, nUpdated{0};
    std::map<QWidget*, QFont> mapWidgetFonts;
    // Loop through all widgets
    for (QWidget* w : qApp->allWidgets()) {
        if (auto* wt{qobject_cast<QTextEdit*>(w)};
            std::find(vecIgnoreClasses.begin(), vecIgnoreClasses.end(), w->metaObject()->className()) != vecIgnoreClasses.end() ||
            std::find(vecIgnoreObjects.begin(), vecIgnoreObjects.end(), w->objectName().toStdString()) != vecIgnoreObjects.end() ||
            (wt && mapTextEditStyleUpdates.count(wt)))
        {
            // Do not apply styling logic if ignored or handled separately
            continue;
        }
        QFont font = w->font();
        // A stylesheet rule such as `font-size: Npx` leaves the widget with a pixel-sized
        // font, which reports no point size. Convert instead of assuming, and skip the
        // widget outright if no size can be derived -- leaving one widget unscaled beats
        // aborting, and substituting a default would resize a widget the stylesheet
        // deliberately sized.
        const std::optional<double> base_size{internal::effectivePointSize(font, w->logicalDpiY())};
        if (!base_size) {
            continue;
        }
        ++nUpdatable;

        font.setFamily(qApp->font().family());
        font.setWeight(g_font_registry.GetWeightNormal());
        font.setStyleName(qApp->font().styleName());
        font.setStyle(qApp->font().style());

        // Insert/Get the default font size of the widget. Seeded once per widget, so a
        // later stylesheet re-apply cannot compound the scaling. Note this freezes a
        // pixel-derived size at the DPI first seen; moving the window to a screen with a
        // different DPI will not re-honour the stylesheet's pixel intent.
        auto itDefault = mapWidgetDefaultFontSizes.emplace(w, *base_size);

        auto it = mapFontUpdates.find(w);
        if (it != mapFontUpdates.end()) {
            double nSize = it->second.m_point_size;
            if (nSize == -1) {
                nSize = itDefault.first->second;
            }
            font = getFont({it->second.m_font, it->second.m_weight_type, nSize, it->second.m_is_italic});
        } else {
            font.setPointSizeF(g_font_registry.GetScaledFontSize(itDefault.first->second));
        }

        if (w->font() != font) {
            auto itWidgetFont = mapWidgetFonts.emplace(w, font);
            assert(itWidgetFont.second);
            ++nUpdated;
        }
    }
    qDebug().nospace() << qstrprintf("%s - widget counts: updated/updatable/total(%d/%d/%d), removed items: "
                                     "mapWidgetDefaultFontSizes/mapFontUpdates/mapTextEditStyleUpdates(%d/%d/%d)",
                                     __func__, nUpdated, nUpdatable, qApp->allWidgets().size(), nRemovedDefaultFonts,
                                     nRemovedFontUpdates, nRemovedTextEditUpdates);

    // Perform the required font updates
    // NOTE: This is done as separate step to avoid scaling issues due to font inheritance
    //       hence all fonts are calculated and stored in mapWidgetFonts above.
    for (auto it : mapWidgetFonts) {
        it.first->setFont(it.second);
    }

    // Scale the global font size for the classes in the map below. These fonts belong to no
    // widget, so the primary screen supplies the DPI for any pixel-to-point conversion.
    const QScreen* primary_screen{QGuiApplication::primaryScreen()};
    const int screen_dpi_y{primary_screen ? qRound(primary_screen->logicalDotsPerInchY()) : 0};
    for (auto& it : mapClassFontUpdates) {
        QFont fontClass = qApp->font(it.first.c_str());
        if (!it.second) {
            // Leave the entry uncaptured and retry on the next pass if the size is unusable.
            it.second = internal::effectivePointSize(fontClass, screen_dpi_y);
            if (!it.second) {
                continue;
            }
        }
        double dSize = g_font_registry.GetScaledFontSize(*it.second);
        if (fontClass.pointSizeF() != dSize) {
            fontClass.setPointSizeF(dSize);
            qApp->setFont(fontClass, it.first.c_str());
        }
    }

    // Update registered QTextEdit widgets
    for (const auto& [widget, data] : mapTextEditStyleUpdates) {
        setFontBodyHTML(widget, data.html, data.base_size);
    }
}

std::vector<QString> getFonts(bool selectable_only)
{
    std::vector<QString> ret;
    for (const auto& [font, selectable] : g_fonts_known) {
        if (selectable || !selectable_only) { ret.emplace_back(font); }
    }
    return ret;
}

QFont getFontBold()
{
    return getFont({FontWeight::Bold});
}

QFont getFontNormal()
{
    return getFont({FontWeight::Normal});
}

QFont getScaledFont(double baseSize, bool bold, double multiplier)
{
    return getFont({
        bold ? FontWeight::Bold : FontWeight::Normal,
        baseSize * multiplier
    });
}

QFont::Weight FontRegistry::IdxToWeight(int index) const
{
    const auto vecWeights = GetSupportedWeights();
    assert(vecWeights.size() > uint64_t(index));
    return vecWeights.at(index);
}

int FontRegistry::WeightToIdx(const QFont::Weight& weight) const
{
    const auto vecWeights = GetSupportedWeights();
    for (uint64_t index = 0; index < vecWeights.size(); ++index) {
        if (weight == vecWeights.at(index)) {
            return index;
        }
    }
    return -1;
}

QFont fixedPitchFont(bool use_embedded_font)
{
    return getFont({
        use_embedded_font ? ROBOTO_MONO_FONT_STR.toUtf8() : OS_MONO_FONT_STR.toUtf8(),
        FontWeight::Normal
    });
}

void registerWidget(QTextEdit* widget, const QString& html)
{
    if (!widget) return;
    double base_size{FontRegistry::DEFAULT_FONT_SIZE};
    auto it{mapTextEditStyleUpdates.find(widget)};
    if (it != mapTextEditStyleUpdates.end()) {
        // Widget already registered, preserve stored base_size and update HTML
        base_size = it->second.base_size;
        it->second.html = html;
    } else {
        // First registration, capture the widget's native font size, converting it when the
        // widget was sized in pixels. Falls back to DEFAULT_FONT_SIZE if no size is usable.
        base_size = internal::effectivePointSize(widget->font(), widget->logicalDpiY()).value_or(base_size);
        mapTextEditStyleUpdates[widget] = {html, base_size};
    }
    setFontBodyHTML(widget, html, base_size);
}
} // namespace GUIUtil
