#include "bianbustyle.h"
#include <QApplication>
#include <QStyleOption>
#include <QStyleOptionSlider>
#include <QPainter>
#include <QLinearGradient>
#include <QStyleOptionMenuItem>
#include <QStyleOptionButton>
#include <QTimer>
#include <QFile>
#include <QPainterPath>
#include <QMenu>
#include <QStyleFactory>
#include <QRegion>
#include <cmath>

BianbuStyle::BianbuStyle(const QString &configPath)
    : QProxyStyle(QStyleFactory::create(QStringLiteral("Fusion"))), m_configPath(configPath) {
    setObjectName(QStringLiteral("Bianbu"));
    reloadConfig();
    setupWatcher();
}

void BianbuStyle::setupWatcher() {
    if (m_configPath.isEmpty()) return;
    if (!m_watcher.files().contains(m_configPath)) m_watcher.addPath(m_configPath);
    QObject::connect(&m_watcher, &QFileSystemWatcher::fileChanged, this, [this](const QString &path){
        if (!QFile::exists(path)) {
            QTimer::singleShot(500, this, [this,path]{ if (QFile::exists(path)) m_watcher.addPath(path); });
        } else if (!m_watcher.files().contains(path)) {
            m_watcher.addPath(path);
        }
        reloadConfig();
    });
}

void BianbuStyle::reloadConfig() {
    // If no explicit path was given and none adopted yet, attempt to locate a user config and adopt it (enables watch later)
    if (m_configPath.isEmpty()) {
        QString found = ThemeConfig::locateUserConfig();
        if (!found.isEmpty()) {
            m_configPath = found;
            setupWatcher();
        }
    }
    m_cfg = ThemeConfig::load(m_configPath);
    emit themeReloaded();
    for (QWidget *w : QApplication::topLevelWidgets()) w->update();
}

void BianbuStyle::drawComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, QPainter *p, const QWidget *w) const {
    if (cc == CC_ScrollBar) {
        const QStyleOptionSlider *sl = qstyleoption_cast<const QStyleOptionSlider *>(opt);
        if (!sl) { QProxyStyle::drawComplexControl(cc, opt, p, w); return; }

        const QRect groove = subControlRect(CC_ScrollBar, opt, SC_ScrollBarGroove, w);
        const QRect slider = subControlRect(CC_ScrollBar, opt, SC_ScrollBarSlider, w);

        p->save();
        p->setRenderHint(QPainter::Antialiasing, true);

        // 1) Groove: rounded rect with rgba(0,0,0,0.65) and inner-edge gradient shadow clipped to the rounded path
        if (groove.isValid() && !groove.isEmpty()) {
            const QColor grooveColor(0, 0, 0, qRound(0.65 * 255));
            const qreal radius = 2.0;
            QPainterPath groovePath;
            groovePath.addRoundedRect(QRectF(groove), radius, radius);
            p->fillPath(groovePath, grooveColor);

            // Inner shadow: fade from the outer edge inward (vertical: from top; horizontal: from left)
            p->save();
            p->setClipPath(groovePath);
            const QColor shadowEdge(0, 0, 0, qRound(0.5 * 255)); // slightly softer than groove fill for a subtle inner shadow
            if (sl->orientation == Qt::Vertical) {
                QRect band = groove;
                const int h = qMin(20, band.height());
                band.setHeight(h);
                QLinearGradient grad(band.topLeft(), band.bottomLeft());
                grad.setColorAt(0.0, shadowEdge);
                grad.setColorAt(1.0, QColor(0, 0, 0, 0));
                p->fillRect(band, grad);
            } else {
                QRect band = groove;
                const int wBand = qMin(20, band.width());
                band.setWidth(wBand);
                QLinearGradient grad(band.topLeft(), band.topRight());
                grad.setColorAt(0.0, shadowEdge);
                grad.setColorAt(1.0, QColor(0, 0, 0, 0));
                p->fillRect(band, grad);
            }
            p->restore();
        }

        // 2) Slider/handle: rgba(255,255,255,0.2), 2px radius
        if (slider.isValid() && !slider.isEmpty()) {
            const QColor handle(255, 255, 255, qRound(0.2 * 255));
            const qreal radius = 2.0;
            QPainterPath hp;
            hp.addRoundedRect(slider, radius, radius);
            p->fillPath(hp, handle);
        }

        p->restore();
        return; // fully handled
    }

    QProxyStyle::drawComplexControl(cc, opt, p, w);
}

int BianbuStyle::pixelMetric(PixelMetric m, const QStyleOption *opt, const QWidget *w) const {
    switch (m) {
        case PM_ScrollBarExtent:
            return 4;
        case PM_ScrollBarSliderMin:
            return 24;
        case PM_MenuHMargin:
        case PM_MenuVMargin:
            return 5; // popup inner padding
        case PM_MenuPanelWidth:
            return 0; // we'll paint our own 1px border in PE_PanelMenu
        default: break;
    }
    return QProxyStyle::pixelMetric(m,opt,w);
}

QPalette BianbuStyle::standardPalette() const { return baseStyle()->standardPalette(); }

void BianbuStyle::polish(QPalette &pal) { QProxyStyle::polish(pal); }

void BianbuStyle::polish(QWidget *w) {
    QProxyStyle::polish(w);
    if (auto *menu = qobject_cast<QMenu *>(w)) {
        // If not yet created, request no compositor shadow; avoids re-entrant flag churn on Qt6
        if (!menu->testAttribute(Qt::WA_WState_Created)) {
            menu->setWindowFlag(Qt::NoDropShadowWindowHint, true);
        }
        // Enable translucent background so corners outside our rounded path are transparent
        menu->setAttribute(Qt::WA_TranslucentBackground, true);
        // Install event filter to update rounded mask on show/resize
        menu->installEventFilter(this);
        // Apply initial rounded mask
        const int radius = 6;
        QPainterPath path;
        path.addRoundedRect(menu->rect(), radius, radius);
        QRegion mask;
        const auto polys = path.toFillPolygons();
        for (const QPolygonF &poly : polys) mask += QRegion(poly.toPolygon());
        menu->setMask(mask);
    }
}

void BianbuStyle::unpolish(QWidget *w) {
    if (auto *menu = qobject_cast<QMenu *>(w)) {
        // Restore attributes and mask
        menu->setAttribute(Qt::WA_TranslucentBackground, false);
        menu->removeEventFilter(this);
        menu->clearMask();
    }
    QProxyStyle::unpolish(w);
}

bool BianbuStyle::eventFilter(QObject *obj, QEvent *ev) {
    if (auto *menu = qobject_cast<QMenu *>(obj)) {
        if (ev->type() == QEvent::Show || ev->type() == QEvent::Resize) {
            const int radius = 6;
            QPainterPath path;
            path.addRoundedRect(menu->rect(), radius, radius);
            QRegion mask;
            const auto polys = path.toFillPolygons();
            for (const QPolygonF &poly : polys) mask += QRegion(poly.toPolygon());
            menu->setMask(mask);
        }
    }
    return QProxyStyle::eventFilter(obj, ev);
}

void BianbuStyle::drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, QPainter *p, const QWidget *w) const {
    if (pe == PE_PanelMenu) {
        p->save();
        p->setRenderHint(QPainter::Antialiasing, true);
        const QRect r = opt->rect;
        const qreal radius = 6.0;
        QPainterPath path;
        path.addRoundedRect(QRectF(r), radius, radius);
        // Fusion-like background colors
        const QColor bg = opt->palette.base().color().lighter(108);
        const QColor border = opt->palette.window().color().darker(160);
        p->fillPath(path, bg);
        QPen pen(border, 1.0);
        p->setPen(pen);
        p->drawPath(path);
        p->restore();
        return;
    }
    QProxyStyle::drawPrimitive(pe, opt, p, w);
}

static inline QString splitShortcut(const QString &text, QString *shortcutOut) {
    int t = text.indexOf(QLatin1Char('\t'));
    if (t >= 0) {
        if (shortcutOut) *shortcutOut = text.mid(t + 1);
        return text.left(t);
    } else {
        if (shortcutOut) shortcutOut->clear();
        return text;
    }
}

// SVG-equivalent chevron path (viewport 16x16) matching indicator.svg
static QPainterPath indicatorChevronPath16()
{
    QPainterPath path;
    path.moveTo(9.18306, 3.80806);
    path.cubicTo(9.42714, 3.56398, 9.82277, 3.56398, 10.0669, 3.80806);
    path.lineTo(13.8169, 7.55807);
    path.cubicTo(14.0609, 7.80215, 14.0609, 8.19779, 13.8169, 8.44187);
    path.lineTo(10.0669, 12.1919);
    path.cubicTo(9.82277, 12.436, 9.42714, 12.436, 9.18306, 12.1919);
    path.cubicTo(8.93898, 11.9478, 8.93898, 11.5522, 9.18306, 11.3081);
    path.lineTo(12.4912, 7.99997);
    path.lineTo(9.18306, 4.69185);
    path.cubicTo(8.93898, 4.44777, 8.93898, 4.05214, 9.18306, 3.80806);
    path.closeSubpath();
    return path;
}

void BianbuStyle::drawControl(ControlElement element, const QStyleOption *option, QPainter *p, const QWidget *w) const {
    if (element == CE_MenuItem) {
        if (const QStyleOptionMenuItem *mi = qstyleoption_cast<const QStyleOptionMenuItem *>(option)) {
            const QRect r = mi->rect;
            const bool enabled = mi->state & State_Enabled;
            const bool selected = (mi->state & State_Selected) && enabled;

            // Separator
            if (mi->menuItemType == QStyleOptionMenuItem::Separator) {
                p->save();
                const int leftMargin = 28;
                const int rightMargin = 8;
                const int y = r.center().y();
                QColor sep(255, 255, 255, qRound(0.10 * 255));
                QPen pen(sep);
                pen.setWidth(1);
                p->setPen(pen);
                p->drawLine(r.left() + leftMargin, y, r.right() - rightMargin, y);
                p->restore();
                return;
            }

            // Constants per spec
            const int padL = 6, padR = 8, padV = 4, gap = 4;
            const int slot = 16; // icon/check slot size
            const int minTextW = 90;
            const int minShortW = 40;
            const int minCellH = 24;

            // Selection background and hover/pressed overlays with rounded corners
            const int itemRadius = 4;
            QPainterPath itemPath;
            itemPath.addRoundedRect(QRectF(r).adjusted(0.5, 0.5, -0.5, -0.5), itemRadius, itemRadius);
            if (selected) {
                p->save();
                p->fillPath(itemPath, option->palette.highlight());
                p->restore();
            }
            // Overlay: hover and pressed
            const bool pressed = (mi->state & State_Sunken);
            const bool hovered = selected || (mi->state & State_MouseOver);
            if (pressed || hovered) {
                p->save();
                const qreal a = pressed ? 0.05 : 0.03;
                QColor overlay(255, 255, 255, qRound(a * 255));
                p->fillPath(itemPath, overlay);
                p->restore();
            }

            // Content rect
            QRect cr = r.adjusted(padL, padV, -padR, -padV);
            const int cy = cr.center().y();

            // Leading slot for icon/check
            QRect slotRect(cr.left(), cy - slot/2, slot, slot);

            // Determine what to draw in slot
            const bool checkable = mi->checkType != QStyleOptionMenuItem::NotCheckable;
            const bool exclusive = (mi->checkType & QStyleOptionMenuItem::Exclusive);
            const bool checked = mi->checked || (mi->state & State_On);
            const bool hasIcon = !mi->icon.isNull();

            p->save();
            if (checkable) {
                // Draw checkbox or radio at 16x16, vertical centered
                QStyleOptionButton indOpt;
                indOpt.QStyleOption::operator=(*option);
                indOpt.rect = slotRect;
                indOpt.state &= ~State_HasFocus; // indicators in menu shouldn't show focus ring
                if (enabled) indOpt.state |= State_Enabled; else indOpt.state &= ~State_Enabled;
                if (checked) indOpt.state |= State_On; else indOpt.state |= State_Off;
                if (exclusive) {
                    proxy()->drawPrimitive(PE_IndicatorRadioButton, &indOpt, p, w);
                } else {
                    proxy()->drawPrimitive(PE_IndicatorCheckBox, &indOpt, p, w);
                }
            } else if (hasIcon) {
                // Draw icon scaled to 16x16
                QIcon::Mode mode = !enabled ? QIcon::Disabled : selected ? QIcon::Selected : QIcon::Normal;
                QPixmap pm = mi->icon.pixmap(QSize(slot, slot), mode, checked ? QIcon::On : QIcon::Off);
                const int pw = pm.width() / pm.devicePixelRatio();
                const int ph = pm.height() / pm.devicePixelRatio();
                QRect pmr(0, 0, pw, ph);
                pmr.moveCenter(slotRect.center());
                p->drawPixmap(pmr.topLeft(), pm);
            }
            p->restore();

            // Advance x after slot
            int x = slotRect.right() + 1 + gap;
            const int rightLimit = cr.right();

            // Split text and shortcut by tab
            QString shortcutText;
            const QString mainText = splitShortcut(mi->text, &shortcutText);

            // Decide whether to show shortcut or arrow
            const bool isSubMenu = mi->menuItemType == QStyleOptionMenuItem::SubMenu;

            // Measure fonts
            QFontMetrics fm(mi->font);

            // Reserve right-side area (shortcut or arrow)
            int arrowW = 12; // arrow width (square area)
            int scW = isSubMenu ? arrowW : qMax(minShortW, fm.horizontalAdvance(shortcutText));

            // Text rect fills remaining space minus gap+right reserve
            const int rightReserve = scW > 0 ? (gap + scW) : 0;
            QRect textRect(x, cr.top(), qMax(minTextW, rightLimit - x - rightReserve), cr.height());
            textRect.setRight(qMin(textRect.right(), rightLimit - rightReserve));

            // Colors and text flags (preserve mnemonics)
            QColor textColor = selected ? option->palette.highlightedText().color() : option->palette.text().color();
            p->save();
            p->setPen(textColor);
            QFont font = mi->font;
            if (mi->menuItemType == QStyleOptionMenuItem::DefaultItem)
                font.setBold(true);
            p->setFont(font);
            // draw full text (no elide), let sizeFromContents guarantee enough width; keep mnemonic underline policy
            int textFlags = Qt::AlignVCenter | Qt::AlignLeft | Qt::TextSingleLine | Qt::TextShowMnemonic | Qt::TextDontClip;
            if (!proxy()->styleHint(SH_UnderlineShortcut, mi, w)) textFlags |= Qt::TextHideMnemonic;
            proxy()->drawItemText(p, textRect, textFlags, option->palette, enabled, mainText, selected ? QPalette::HighlightedText : QPalette::Text);
            p->restore();

            if (!isSubMenu) {
                // Shortcut container on the right, width auto-expands with text (min 40)
                QRect scRect(rightLimit - scW, cr.top(), scW, cr.height());
                // If shortcut text provided via tab, use it; otherwise leave empty but reserve width
                if (!shortcutText.isEmpty()) {
                    p->save();
                    p->setPen(textColor);
                    proxy()->drawItemText(p, scRect, Qt::AlignVCenter | Qt::AlignRight | Qt::TextSingleLine | Qt::TextDontClip, option->palette, enabled, shortcutText, selected ? QPalette::HighlightedText : QPalette::Text);
                    p->restore();
                }
            } else {
                // Draw submenu arrow by manually rendering a chevron path matching the SVG shape
                QRect arrowRect(rightLimit - scW, cy - arrowW/2, arrowW, arrowW);
                const bool rtl = (option->direction == Qt::RightToLeft);

                // Build the canonical chevron path (16x16 viewport) and fit into arrowRect
                const QPainterPath basePath = indicatorChevronPath16();
                const QRectF br = basePath.boundingRect();

                // Compute uniform scale to fit within arrowRect and center it
                const qreal sx = arrowRect.width()  / br.width();
                const qreal sy = arrowRect.height() / br.height();
                const qreal s = qMin(sx, sy);

                QTransform tr;
                tr.translate(arrowRect.center().x(), arrowRect.center().y());
                tr.scale(rtl ? -s : s, s);
                tr.translate(-br.center().x(), -br.center().y());

                // Choose color: selected用高亮文本色，未选用正文色；禁用用25%白（与素材一致）
                QColor col;
                if (!enabled) {
                    col = QColor(255, 255, 255, qRound(0.25 * 255));
                } else {
                    col = selected ? option->palette.highlightedText().color()
                                   : option->palette.text().color();
                }

                p->save();
                p->setRenderHint(QPainter::Antialiasing, true);
                p->setPen(Qt::NoPen);
                p->setBrush(col);
                p->drawPath(tr.map(basePath));
                p->restore();
            }
            return;
        }
    }
    QProxyStyle::drawControl(element, option, p, w);
}

QSize BianbuStyle::sizeFromContents(ContentsType type, const QStyleOption *option, const QSize &contentsSize, const QWidget *w) const {
    if (type == CT_MenuItem) {
        if (const QStyleOptionMenuItem *mi = qstyleoption_cast<const QStyleOptionMenuItem *>(option)) {
            const int padL = 6, padR = 8, padV = 4, gap = 4;
            const int slot = 16;
            const int minTextW = 90;
            const int minShortW = 40;
            const int minCellH = 24;

            // Split text/shortcut
            QString sc;
            QString main = splitShortcut(mi->text, &sc);
            QFont font = mi->font;
            if (mi->menuItemType == QStyleOptionMenuItem::DefaultItem) font.setBold(true);
            QFontMetrics fm(font);
            int textW = qMax(minTextW, fm.horizontalAdvance(main));

            bool isSub = mi->menuItemType == QStyleOptionMenuItem::SubMenu;
            int rightReserve = isSub ? 12 : qMax(minShortW, fm.horizontalAdvance(sc));

            // Add a tiny fudge (+2) to avoid rounding/elision on HiDPI
            int width = padL + slot + gap + textW + (rightReserve > 0 ? (gap + rightReserve) : 0) + padR + 2;
            int height = padV * 2 + qMax(minCellH, slot);

            // Ensure separator gets minimal height
            if (mi->menuItemType == QStyleOptionMenuItem::Separator) {
                height = qMax(2, padV * 2 + 1);
                width = qMax(width, contentsSize.width());
            }

            return QSize(width, height);
        }
    }
    return QProxyStyle::sizeFromContents(type, option, contentsSize, w);
}

