#include "popup_menu_window.h"

#include <QEnterEvent>
#include <QFontMetrics>
#include <QHBoxLayout>
#include <QLabel>
#include <QMouseEvent>
#include <QPainter>
#include <QPainterPath>
#include <QPaintEvent>
#include <QPen>
#include <QResizeEvent>
#include <QVBoxLayout>

namespace {
constexpr int kOuterPadding = 5;
constexpr int kVerticalSpacing = 1;
constexpr int kRowHorizontalPadding = 8;
constexpr int kRowVerticalPadding = 3;
constexpr int kRowSpacing = 6;
constexpr int kPopupCornerRadius = 12;
constexpr int kRowCornerRadius = 7;
constexpr int kCompactFontSizePx = 11;
constexpr int kGlyphColumnWidth = 12;
constexpr int kMaxWrappedLines = 2;

QColor popupBackgroundColor()
{
    return QColor(18, 19, 22, 252);
}

QColor popupBorderColor()
{
    return QColor(255, 255, 255, 18);
}

QString wrapTextToLines(const QString &text, const QFontMetrics &metrics, int width, int maxLines)
{
    if (text.isEmpty() || width <= 0 || maxLines <= 1) {
        return metrics.elidedText(text, Qt::ElideRight, qMax(width, 0));
    }

    QStringList lines;
    QString current;
    int index = 0;

    while (index < text.size() && lines.size() < maxLines - 1) {
        const QChar ch = text.at(index);
        const QString candidate = current + ch;
        if (!current.isEmpty() && metrics.horizontalAdvance(candidate) > width) {
            lines << current;
            current.clear();
            continue;
        }
        current = candidate;
        ++index;
    }

    QString remainder = current + text.mid(index);
    lines << metrics.elidedText(remainder, Qt::ElideRight, width);
    return lines.join('\n');
}

class PopupActionRow : public QFrame {
    Q_OBJECT

public:
    PopupActionRow(QAction *action, bool launcher, QWidget *parent = nullptr)
        : QFrame(parent)
        , action_(action)
        , launcher_(launcher)
    {
        setObjectName(QStringLiteral("popupActionRow"));
        setMouseTracking(true);
        setFrameStyle(QFrame::NoFrame);

        auto *layout = new QHBoxLayout(this);
        layout->setContentsMargins(kRowHorizontalPadding, kRowVerticalPadding, kRowHorizontalPadding, kRowVerticalPadding);
        layout->setSpacing(kRowSpacing);

        checkLabel_ = new QLabel(this);
        checkLabel_->setMinimumWidth(kGlyphColumnWidth);
        textLabel_ = new QLabel(this);
        textLabel_->setMinimumWidth(0);
        textLabel_->setWordWrap(false);
        textLabel_->setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
        textLabel_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
        arrowLabel_ = new QLabel(this);
        arrowLabel_->setMinimumWidth(kGlyphColumnWidth);

        layout->addWidget(checkLabel_);
        layout->addWidget(textLabel_, 1);
        layout->addWidget(arrowLabel_);

        syncFromAction();
        connect(action_, &QAction::changed, this, &PopupActionRow::syncFromAction);
    }

signals:
    void hovered(QAction *action);
    void triggered(QAction *action);

protected:
    void enterEvent(QEnterEvent *event) override
    {
        QFrame::enterEvent(event);
        hovered_ = true;
        updateStyle();
        if (action_ && action_->isEnabled()) {
            emit hovered(action_);
        }
    }

    void leaveEvent(QEvent *event) override
    {
        QFrame::leaveEvent(event);
        hovered_ = false;
        updateStyle();
    }

    void mouseReleaseEvent(QMouseEvent *event) override
    {
        if (event->button() == Qt::LeftButton && action_ && action_->isEnabled()) {
            emit triggered(action_);
        }
        QFrame::mouseReleaseEvent(event);
    }

    void resizeEvent(QResizeEvent *event) override
    {
        QFrame::resizeEvent(event);
        updateDisplayedText();
    }

private slots:
    void syncFromAction()
    {
        if (!action_) {
            return;
        }
        setVisible(action_->isVisible());
        rawText_ = action_->text();
        checkLabel_->setText(action_->isCheckable() && action_->isChecked() ? QStringLiteral("✓")
                                                                             : QStringLiteral(""));
        arrowLabel_->setText(launcher_ ? QStringLiteral("▸") : QStringLiteral(""));
        updateDisplayedText();
        updateStyle();
    }

private:
    void updateDisplayedText()
    {
        const int availableWidth = qMax(0, textLabel_->width());
        if (availableWidth <= 0) {
            textLabel_->setText(rawText_);
            return;
        }
        const QFontMetrics metrics(textLabel_->font());
        textLabel_->setText(wrapTextToLines(rawText_, metrics, availableWidth, kMaxWrappedLines));
    }

    void updateStyle()
    {
        const bool enabled = action_ && action_->isEnabled();
        const QString fg = enabled ? QStringLiteral("#F2F3F5") : QStringLiteral("#8C8D91");
        const QString bg = hovered_ && enabled ? QStringLiteral("rgba(40,115,255,0.95)")
                                               : QStringLiteral("transparent");
        setStyleSheet(QStringLiteral(
                          "QFrame#popupActionRow {"
                          "border-radius: %1px;"
                          "background:%2;"
                          "}"
                          "QLabel {"
                          "color:%3;"
                          "font-size: %4px;"
                          "}").
                          arg(QString::number(kRowCornerRadius), bg, fg, QString::number(kCompactFontSizePx)));
    }

    QAction *action_ = nullptr;
    bool launcher_ = false;
    bool hovered_ = false;
    QString rawText_;
    QLabel *checkLabel_ = nullptr;
    QLabel *textLabel_ = nullptr;
    QLabel *arrowLabel_ = nullptr;
};
}

PopupMenuWindow::PopupMenuWindow(QWidget *parent)
    : QFrame(parent)
{
    setWindowFlags(Qt::Tool | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint);
    setAttribute(Qt::WA_ShowWithoutActivating);
    setAttribute(Qt::WA_TranslucentBackground);
    setMouseTracking(true);

    auto *layout = new QVBoxLayout(this);
    layout->setContentsMargins(kOuterPadding, kOuterPadding, kOuterPadding, kOuterPadding);
    layout->setSpacing(kVerticalSpacing);
}

void PopupMenuWindow::clearRows()
{
    auto *layout = qobject_cast<QVBoxLayout *>(this->layout());
    while (layout && layout->count() > 0) {
        QLayoutItem *item = layout->takeAt(0);
        if (item->widget()) {
            item->widget()->deleteLater();
        }
        delete item;
    }
    rowWidgets_.clear();
    actionRows_.clear();
    actionCount_ = 0;
}

void PopupMenuWindow::setActions(const QList<QAction *> &actions, const QSet<QAction *> &launcherActions)
{
    clearRows();
    auto *layout = qobject_cast<QVBoxLayout *>(this->layout());
    if (!layout) {
        return;
    }

    for (QAction *action : actions) {
        if (!action || !action->isVisible()) {
            continue;
        }
        if (action->isSeparator()) {
            auto *line = new QFrame(this);
            line->setFrameShape(QFrame::HLine);
            line->setStyleSheet(QStringLiteral("background: rgba(255,255,255,0.08); min-height: 1px;"));
            layout->addWidget(line);
            rowWidgets_.append(line);
            continue;
        }

        auto *row = new PopupActionRow(action, launcherActions.contains(action), this);
        connect(row, &PopupActionRow::hovered, this, &PopupMenuWindow::actionHovered);
        connect(row, &PopupActionRow::triggered, this, &PopupMenuWindow::actionTriggered);
        layout->addWidget(row);
        rowWidgets_.append(row);
        actionRows_.insert(action, row);
        ++actionCount_;
    }

    refreshGeometry();
}

void PopupMenuWindow::setPreferredMaxWidth(int width)
{
    preferredMaxWidth_ = qMax(0, width);
    refreshGeometry();
}

int PopupMenuWindow::actionCount() const
{
    return actionCount_;
}

bool PopupMenuWindow::containsGlobal(const QPoint &globalPos) const
{
    return isVisible() && geometry().contains(globalPos);
}

QRect PopupMenuWindow::actionGlobalRect(QAction *action) const
{
    QWidget *row = actionRows_.value(action, nullptr);
    if (!row) {
        return {};
    }
    return QRect(row->mapToGlobal(QPoint(0, 0)), row->size());
}

QColor PopupMenuWindow::backgroundFillColor() const
{
    return popupBackgroundColor();
}

QSize PopupMenuWindow::sizeHint() const
{
    QSize size = QFrame::sizeHint();
    if (preferredMaxWidth_ > 0) {
        size.setWidth(qMin(size.width(), preferredMaxWidth_));
    }
    return size;
}

void PopupMenuWindow::refreshGeometry()
{
    if (preferredMaxWidth_ > 0) {
        setMaximumWidth(preferredMaxWidth_);
    } else {
        setMaximumWidth(QWIDGETSIZE_MAX);
    }
    adjustSize();
}

void PopupMenuWindow::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event);

    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing, true);

    const QRectF bounds = rect().adjusted(0.5, 0.5, -0.5, -0.5);
    QPainterPath path;
    path.addRoundedRect(bounds, kPopupCornerRadius, kPopupCornerRadius);
    painter.fillPath(path, backgroundFillColor());
    painter.setPen(QPen(popupBorderColor(), 1.0));
    painter.drawPath(path);
}

#include "popup_menu_window.moc"
