// Copyright (C) 2026 Petr Mironychev
// SPDX-License-Identifier: GPL-3.0-or-later
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE

#include "AgentSelectionDialog.hpp"

#include "AgentSlotWidget.hpp"
#include "PipelinesConfig.hpp"
#include "SettingsTr.hpp"

#include <coreplugin/icore.h>

#include <AgentFactory.hpp>

#include <QDialogButtonBox>
#include <QEnterEvent>
#include <QEvent>
#include <QFont>
#include <QHBoxLayout>
#include <QLineEdit>
#include <QMap>
#include <QMouseEvent>
#include <QPushButton>
#include <QScopedValueRollback>
#include <QScrollArea>
#include <QTimer>
#include <QVBoxLayout>
#include <algorithm>

namespace QodeAssist::Settings {

// -- ListRowCard -------------------------------------------------------

ListRowCard::ListRowCard(QWidget *parent)
    : QFrame(parent)
{
    setObjectName(QStringLiteral("ListRowCard"));
    setAttribute(Qt::WA_StyledBackground, true);
    setCursor(Qt::PointingHandCursor);
    setFrameShape(QFrame::NoFrame);
    applyTheme();
}

bool ListRowCard::matches(const QString &needle) const
{
    if (needle.isEmpty())
        return true;
    return m_searchHaystack.contains(needle.toLower());
}

void ListRowCard::setSelected(bool selected)
{
    if (m_selected == selected)
        return;
    m_selected = selected;
    applyTheme();
}

void ListRowCard::buildSearchHaystack(const QStringList &parts)
{
    m_searchHaystack = parts.join(QLatin1Char(' ')).toLower();
}

void ListRowCard::mousePressEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton)
        emit clicked();
    QFrame::mousePressEvent(event);
}

void ListRowCard::mouseDoubleClickEvent(QMouseEvent *event)
{
    if (event->button() == Qt::LeftButton)
        emit activated();
    QFrame::mouseDoubleClickEvent(event);
}

void ListRowCard::enterEvent(QEnterEvent *event)
{
    QFrame::enterEvent(event);
    m_hover = true;
    applyTheme();
}

void ListRowCard::leaveEvent(QEvent *event)
{
    QFrame::leaveEvent(event);
    m_hover = false;
    applyTheme();
}

void ListRowCard::changeEvent(QEvent *event)
{
    QFrame::changeEvent(event);
    if (m_inApplyTheme)
        return;
    if (event->type() == QEvent::PaletteChange || event->type() == QEvent::StyleChange)
        applyTheme();
}

void ListRowCard::applyTheme()
{
    if (m_inApplyTheme)
        return;
    QScopedValueRollback<bool> guard(m_inApplyTheme, true);

    const auto t = CardStyle::toneFor(CardStyle::isDark(palette()));
    QString bg = t.bg;
    QString bd = t.cardBd;
    if (m_selected) {
        bg = t.selectedBg;
        bd = t.selectedBd;
    } else if (m_hover) {
        bg = t.hoverBg;
    }
    setStyleSheet(QStringLiteral(
                      "#ListRowCard { background-color: %1; border: 1px solid %2; }")
                      .arg(bg, bd));
}

// -- AgentRowCard ------------------------------------------------------

AgentRowCard::AgentRowCard(const AgentConfig &cfg, QWidget *parent)
    : ListRowCard(parent)
{
    setItemName(cfg.name);
    QStringList haystack{cfg.name, cfg.providerInstance, cfg.model,
                         cfg.description, cfg.role,
                         cfg.endpoint};
    haystack += cfg.tags;
    buildSearchHaystack(haystack);

    auto *name = new QLabel(cfg.name, this);
    QFont nameFont = name->font();
    nameFont.setBold(true);
    nameFont.setPixelSize(13);
    name->setFont(nameFont);
    name->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred);

    QLabel *model = nullptr;
    if (!cfg.model.isEmpty()) {
        model = new QLabel(QStringLiteral("· %1").arg(cfg.model), this);
        model->setFont(CardStyle::monoFont(11));
        model->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred);
        model->setMinimumWidth(0);
    }

    Pill *sourcePill = nullptr;
    if (cfg.isUserSource()) {
        sourcePill = new Pill(
            Pill::User,
            cfg.overridesBundled ? Tr::tr("Override") : Tr::tr("User"),
            this);
    }

    auto *description = new QLabel(this);
    description->setWordWrap(false);
    QFont descFont = description->font();
    descFont.setItalic(true);
    description->setFont(descFont);
    description->setText(cfg.description.isEmpty()
                             ? Tr::tr("No description provided.")
                             : cfg.description);
    description->setMinimumWidth(0);
    description->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred);
    description->setTextInteractionFlags(Qt::NoTextInteraction);

    QStringList endpointParts;
    if (!cfg.endpoint.isEmpty())
        endpointParts << cfg.endpoint;
    endpointParts << (cfg.enableThinking ? Tr::tr("thinking") : Tr::tr("no-thinking"));
    endpointParts << (cfg.enableTools ? Tr::tr("tools") : Tr::tr("no-tools"));

    auto *endpoint = new QLabel(endpointParts.join(QStringLiteral(" · ")), this);
    endpoint->setFont(CardStyle::monoFont(11));
    endpoint->setMinimumWidth(0);
    endpoint->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred);
    endpoint->setTextInteractionFlags(Qt::NoTextInteraction);

    auto *headerRow = new QHBoxLayout;
    headerRow->setContentsMargins(0, 0, 0, 0);
    headerRow->setSpacing(6);
    headerRow->addWidget(name);
    if (model)
        headerRow->addWidget(model, 1);
    else
        headerRow->addStretch(1);
    if (sourcePill)
        headerRow->addWidget(sourcePill);

    auto *outer = new QVBoxLayout(this);
    outer->setContentsMargins(10, 8, 10, 8);
    outer->setSpacing(3);
    outer->addLayout(headerRow);
    outer->addWidget(description);
    outer->addWidget(endpoint);

    if (!cfg.tags.isEmpty()) {
        constexpr int kMaxTagPills = 4;
        auto *tagsRow = new QHBoxLayout;
        tagsRow->setContentsMargins(0, 0, 0, 0);
        tagsRow->setSpacing(4);
        const int tagCount = cfg.tags.size();
        for (int i = 0; i < std::min(tagCount, kMaxTagPills); ++i)
            tagsRow->addWidget(new Pill(Pill::Tag, cfg.tags.at(i), this));
        if (tagCount > kMaxTagPills) {
            auto *more = new Pill(
                Pill::Tag,
                QStringLiteral("+%1").arg(tagCount - kMaxTagPills),
                this);
            more->setToolTip(cfg.tags.mid(kMaxTagPills).join(QStringLiteral(", ")));
            tagsRow->addWidget(more);
        }
        tagsRow->addStretch(1);
        outer->addLayout(tagsRow);
    }

    const auto t = CardStyle::toneFor(CardStyle::isDark(palette()));
    QPalette descPal = description->palette();
    descPal.setColor(QPalette::WindowText,
                     QColor(cfg.description.isEmpty() ? t.textFaint : t.textSoft));
    description->setPalette(descPal);
    QPalette endPal = endpoint->palette();
    endPal.setColor(QPalette::WindowText, QColor(t.textFaint));
    endpoint->setPalette(endPal);
    if (model) {
        QPalette mp = model->palette();
        mp.setColor(QPalette::WindowText, QColor(t.textMute));
        model->setPalette(mp);
    }

    QString tooltip;
    if (!cfg.description.isEmpty())
        tooltip += cfg.description + QStringLiteral("\n\n");
    if (!cfg.providerInstance.isEmpty())
        tooltip += Tr::tr("Provider instance: %1\n").arg(cfg.providerInstance);
    if (!cfg.role.isEmpty())
        tooltip += Tr::tr("Role: %1\n").arg(cfg.role);
    if (!cfg.endpoint.isEmpty())
        tooltip += Tr::tr("Endpoint: %1\n").arg(cfg.endpoint);
    setToolTip(tooltip.trimmed());
}

// -- ProviderSection ---------------------------------------------------

ProviderSection::ProviderSection(const QString &name, QWidget *parent)
    : QWidget(parent)
{
    m_arrow = new QLabel(QStringLiteral("▾"));
    m_label = new QLabel(name);
    CardStyle::applySectionFont(m_label);

    QFont arrowFont = m_label->font();
    arrowFont.setCapitalization(QFont::MixedCase);
    m_arrow->setFont(arrowFont);
    QPalette ap = m_arrow->palette();
    ap.setColor(QPalette::WindowText, ap.color(QPalette::Mid));
    m_arrow->setPalette(ap);

    m_header = new QFrame;
    m_header->setObjectName(QStringLiteral("ProviderHeader"));
    m_header->setCursor(Qt::PointingHandCursor);
    m_header->setFrameShape(QFrame::NoFrame);
    auto *headerLayout = new QHBoxLayout(m_header);
    headerLayout->setContentsMargins(2, 4, 2, 2);
    headerLayout->setSpacing(6);
    headerLayout->addWidget(m_arrow);
    headerLayout->addWidget(m_label);
    headerLayout->addStretch(1);
    m_header->installEventFilter(this);

    m_content = new QWidget;
    m_contentLayout = new QVBoxLayout(m_content);
    m_contentLayout->setContentsMargins(0, 0, 0, 0);
    m_contentLayout->setSpacing(4);
    m_content->setVisible(false);
    m_arrow->setText(QStringLiteral("▸"));
    m_expanded = false;

    auto *outer = new QVBoxLayout(this);
    outer->setContentsMargins(0, 0, 0, 0);
    outer->setSpacing(0);
    outer->addWidget(m_header);
    outer->addWidget(m_content);
}

void ProviderSection::addCard(ListRowCard *card)
{
    m_contentLayout->addWidget(card);
    m_cards.append(card);
}

int ProviderSection::applyFilter(const QString &needle)
{
    int visible = 0;
    for (auto *card : m_cards) {
        const bool show = card->matches(needle);
        card->setVisible(show);
        if (show)
            ++visible;
    }
    return visible;
}

void ProviderSection::setExpanded(bool expanded)
{
    if (m_expanded == expanded)
        return;
    m_expanded = expanded;
    m_content->setVisible(expanded);
    m_arrow->setText(expanded ? QStringLiteral("▾") : QStringLiteral("▸"));
}

bool ProviderSection::eventFilter(QObject *watched, QEvent *event)
{
    if (watched == m_header && event->type() == QEvent::MouseButtonRelease) {
        auto *me = static_cast<QMouseEvent *>(event);
        if (me->button() == Qt::LeftButton) {
            setExpanded(!m_expanded);
            return true;
        }
    }
    return QWidget::eventFilter(watched, event);
}

// -- AgentSelectionDialog ----------------------------------------------

AgentSelectionDialog::AgentSelectionDialog(
    const std::vector<AgentConfig> &configs,
    const QString &currentName,
    AgentFactory *agentFactory,
    QWidget *parent)
    : QDialog(parent)
    , m_agentFactory(agentFactory)
{
    setWindowTitle(Tr::tr("Change Agent"));
    resize(720, 600);
    setSizeGripEnabled(true);

    if (!m_agentFactory)
        m_localConfigs = configs;

    m_filter = new QLineEdit(this);
    m_filter->setPlaceholderText(
        Tr::tr("Filter by name, provider, model, template, description…"));
    m_filter->setClearButtonEnabled(true);

    auto *topRow = new QHBoxLayout;
    topRow->setContentsMargins(0, 0, 0, 0);
    topRow->setSpacing(6);
    topRow->addWidget(m_filter, 1);

    m_scroll = new QScrollArea(this);
    m_scroll->setWidgetResizable(true);
    m_scroll->setFrameShape(QFrame::StyledPanel);
    m_scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);

    auto *buttons
        = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
    m_okButton = buttons->button(QDialogButtonBox::Ok);
    m_okButton->setText(Tr::tr("Change"));
    m_okButton->setEnabled(false);

    auto *layout = new QVBoxLayout(this);
    layout->addLayout(topRow);
    layout->addWidget(m_scroll);
    layout->addWidget(buttons);

    rebuild(currentName);

    connect(m_filter, &QLineEdit::textChanged, this,
            [this](const QString &text) { applyFilter(text); });
    connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
    connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
}

void AgentSelectionDialog::selectCard(ListRowCard *card)
{
    if (m_currentCard == card)
        return;
    if (m_currentCard)
        m_currentCard->setSelected(false);
    m_currentCard = card;
    if (m_currentCard) {
        m_currentCard->setSelected(true);
        m_selectedName = m_currentCard->itemName();
    } else {
        m_selectedName.clear();
    }
    if (m_okButton)
        m_okButton->setEnabled(!m_selectedName.isEmpty());
}

void AgentSelectionDialog::rebuild(const QString &currentName)
{
    m_sections.clear();
    m_currentCard = nullptr;
    m_selectedName.clear();
    if (m_okButton)
        m_okButton->setEnabled(false);

    const auto &configs
        = m_agentFactory ? m_agentFactory->configs() : m_localConfigs;

    auto *content = new QWidget;
    auto *contentLayout = new QVBoxLayout(content);
    contentLayout->setContentsMargins(12, 12, 12, 12);
    contentLayout->setSpacing(6);

    QMap<QString, std::vector<const AgentConfig *>> byProvider;
    for (const auto &cfg : configs) {
        if (cfg.hidden) continue; // hidden profiles stay loaded but don't surface in the picker
        const QString key = cfg.providerInstance.isEmpty()
                                ? Tr::tr("(Unknown provider instance)")
                                : cfg.providerInstance;
        byProvider[key].push_back(&cfg);
    }

    AgentRowCard *toSelect = nullptr;
    ProviderSection *sectionToExpand = nullptr;

    for (auto it = byProvider.cbegin(); it != byProvider.cend(); ++it) {
        auto *section = new ProviderSection(it.key());
        auto sortedConfigs = it.value();
        std::sort(sortedConfigs.begin(), sortedConfigs.end(),
                  [](const AgentConfig *a, const AgentConfig *b) { return a->name < b->name; });
        for (const AgentConfig *cfg : sortedConfigs) {
            auto *card = new AgentRowCard(*cfg);
            connect(card, &ListRowCard::clicked, this,
                    [this, card]() { selectCard(card); });
            connect(card, &ListRowCard::activated, this, [this, card]() {
                selectCard(card);
                accept();
            });
            section->addCard(card);
            if (cfg->name == currentName) {
                toSelect = card;
                sectionToExpand = section;
            }
        }
        contentLayout->addWidget(section);
        m_sections.append(section);
    }
    contentLayout->addStretch(1);

    m_scroll->setWidget(content);

    if (sectionToExpand)
        sectionToExpand->setExpanded(true);

    if (toSelect) {
        selectCard(toSelect);
        QTimer::singleShot(0, this, [this, toSelect]() {
            m_scroll->ensureWidgetVisible(toSelect, 0, 60);
        });
    }

    applyFilter(m_filter ? m_filter->text() : QString());
}

void AgentSelectionDialog::applyFilter(const QString &needle)
{
    const QString trimmed = needle.trimmed();
    for (auto *section : m_sections) {
        const int visible = section->applyFilter(trimmed);
        section->setVisible(visible > 0);
        if (!trimmed.isEmpty())
            section->setExpanded(visible > 0);
    }
}

} // namespace QodeAssist::Settings
