// src/screens/backtesting/BacktestingScreen_internal.h
//
// Private helpers shared between the BacktestingScreen.cpp implementation
// files (core, layout, commands, strategies, results). Not intended for use
// outside this folder — kept out of BacktestingScreen.h so consumers don't
// pull in the styling/formatting machinery.
//
// Each helper is `inline` so the header can be included from multiple TUs
// without violating ODR.

#pragma once

#include "core/currency/Currency.h"
#include "services/backtesting/BacktestingTypes.h"

#include <QComboBox>
#include <QCoreApplication>
#include <QDoubleSpinBox>
#include <QEvent>
#include <QJsonValue>
#include <QLayout>
#include <QLayoutItem>
#include <QRegularExpression>
#include <QSizePolicy>
#include <QSpinBox>
#include <QString>
#include <QWidget>

#include <cmath>

namespace fincept::screens::backtesting_internal {

// Event filter that blocks mouse-wheel changes on unfocused combo boxes and
// spin boxes. Without this, scrolling through the config panel accidentally
// changes every widget the cursor passes over.
class WheelGuard : public QObject {
  public:
    using QObject::QObject;

  protected:
    bool eventFilter(QObject* obj, QEvent* event) override {
        if (event->type() == QEvent::Wheel) {
            auto* w = qobject_cast<QWidget*>(obj);
            if (w && !w->hasFocus()) {
                event->ignore();
                return true;
            }
        }
        return QObject::eventFilter(obj, event);
    }
};

// Apply the wheel guard to a widget: StrongFocus so it only gains focus on
// click, plus the event filter to swallow wheel events when unfocused.
inline void guard_wheel(QWidget* w, QObject* filter) {
    w->setFocusPolicy(Qt::StrongFocus);
    w->installEventFilter(filter);
}

// Walk a widget tree and guard every QComboBox / QSpinBox / QDoubleSpinBox.
inline void guard_all_inputs(QWidget* root, QObject* filter) {
    for (auto* combo : root->findChildren<QComboBox*>())
        guard_wheel(combo, filter);
    for (auto* spin : root->findChildren<QSpinBox*>())
        guard_wheel(spin, filter);
    for (auto* dspin : root->findChildren<QDoubleSpinBox*>())
        guard_wheel(dspin, filter);
}

// Shared pill geometry used by every chip on the top bar (brand, provider
// tabs, RUN, status) and by update_provider_buttons() when re-skinning the
// selected vs. inactive provider tab. Both files must agree on the exact
// height/padding for the row to render cleanly, so the constants live here.
inline constexpr int kPillHeight = 24;
inline constexpr int kPillPadH = 10;

inline QString pill_qss(const QString& selector, const QString& fg, const QString& bg, const QString& border,
                        int font_px, const QString& font_family, const QString& weight = QStringLiteral("700")) {
    // Subtract 2px (the 1px top + bottom border) from min/max-height so the
    // total outer box equals kPillHeight in Qt's stylesheet box model.
    return QString("%1 {"
                   "  color:%2; background:%3; border:1px solid %4;"
                   "  font-family:%5; font-size:%6px; font-weight:%7;"
                   "  padding:0 %8px;"
                   "  min-height:%9px; max-height:%9px;"
                   "}")
        .arg(selector, fg, bg, border, font_family)
        .arg(font_px)
        .arg(weight)
        .arg(kPillPadH)
        .arg(kPillHeight - 2);
}

inline void apply_pill_geometry(QWidget* w) {
    w->setFixedHeight(kPillHeight);
    w->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
    w->setContentsMargins(0, 0, 0, 0);
}

inline QString fmt_metric(const QString& key, const QJsonValue& val) {
    using namespace fincept::services::backtest;

    if (val.isString())
        return val.toString();
    if (val.isBool())
        return val.toBool() ? QCoreApplication::translate("BacktestingScreen", "YES")
                            : QCoreApplication::translate("BacktestingScreen", "NO");
    if (!val.isDouble())
        return QString::fromUtf8("—");

    double v = val.toDouble();

    // Count metrics → integer
    if (count_metric_keys().contains(key))
        return QString::number(static_cast<int>(v));

    // Percentage metrics → show as "xx.xx%".
    //
    // Every Python provider emits these as FRACTIONS (vbt_metrics.py divides
    // "Max Drawdown [%]" by 100; win_rate is winning/total; zipline rounds the
    // raw cum-return). The old `abs(v) <= 1.0 ? v*100 : v` heuristic therefore
    // silently understated any result above 100% by a factor of 100 — a 150%
    // total return rendered as "1.50%". Always scale: the contract is fixed.
    if (pct_metric_keys().contains(key))
        return QString("%1%").arg(v * 100.0, 0, 'f', 2);

    // Ratio metrics → show as-is with 2-4 decimals
    if (ratio_metric_keys().contains(key))
        return QString::number(v, 'f', std::abs(v) >= 10.0 ? 2 : 4);

    // Currency-like large values — follow the preferred currency (backtest
    // capital/equity is user-denominated, not market data).
    if (std::abs(v) >= 1e3)
        return cur::money(v, /*compact=*/true);

    return QString::number(v, 'f', 4);
}

inline void clear_layout(QLayout* layout) {
    if (!layout)
        return;
    while (layout->count() > 0) {
        auto* item = layout->takeAt(0);
        if (auto* w = item->widget()) {
            w->setParent(nullptr);
            w->deleteLater();
        } else if (auto* sub = item->layout()) {
            clear_layout(sub);
        }
        delete item;
    }
}

} // namespace fincept::screens::backtesting_internal
