#include "screens/dashboard/widgets/BaseWidget.h"

#include "screens/dashboard/widgets/LoadingOverlay.h"
#include "ui/theme/Theme.h"
#include "ui/theme/ThemeManager.h"

#include <QPalette>
#include <QSize>
#include <QStyle>

namespace fincept::screens::widgets {

BaseWidget::BaseWidget(const QString& title, QWidget* parent, const QString& accent_color)
    : QFrame(parent), accent_color_(accent_color.isEmpty() ? ui::colors::AMBER : accent_color) {
    setObjectName("dashboardBaseWidget");
    setAttribute(Qt::WA_StyledBackground, true);
    setAutoFillBackground(true);

    QPalette pal = palette();
    pal.setColor(QPalette::Window, QColor(ui::colors::BG_SURFACE()));
    setPalette(pal);

    setStyleSheet(QString("#dashboardBaseWidget { background: %1; border: 1px solid %2; border-radius: 2px; }")
                      .arg(ui::colors::BG_SURFACE(), ui::colors::BORDER_BRIGHT()));

    auto* vl = new QVBoxLayout(this);
    vl->setContentsMargins(0, 0, 0, 0);
    vl->setSpacing(0);

    // ── Title bar ──
    title_bar_ = new QWidget(this);
    title_bar_->setFixedHeight(26);
    title_bar_->setStyleSheet(
        QString("background: %1; border-bottom: 1px solid %2;").arg(ui::colors::BG_RAISED(), ui::colors::BORDER_DIM()));

    auto* hl = new QHBoxLayout(title_bar_);
    hl->setContentsMargins(8, 0, 4, 0);
    hl->setSpacing(6);

    // Accent bar
    accent_bar_ = new QLabel;
    accent_bar_->setFixedSize(2, 10);
    accent_bar_->setStyleSheet(QString("background: %1; border-radius: 1px;").arg(accent_color_));
    hl->addWidget(accent_bar_);

    title_label_ = new QLabel(title);
    title_label_->setStyleSheet(QString("color: %1; font-size: 9px; font-weight: bold; letter-spacing: 0.5px; "
                                        "background: transparent; text-transform: uppercase;")
                                    .arg(accent_color_));
    hl->addWidget(title_label_);

    // Loading indicator
    loading_label_ = new QLabel("...");
    loading_label_->setStyleSheet(
        QString("color: %1; font-size: 9px; background: transparent;").arg(ui::colors::AMBER()));
    loading_label_->setVisible(false);
    hl->addWidget(loading_label_);

    hl->addStretch();

    // Config (gear) button — hidden by default until subclass calls set_configurable(true)
    config_btn_ = new QPushButton;
    config_btn_->setFixedSize(20, 20);
    config_btn_->setText("");
    config_btn_->setIcon(style()->standardIcon(QStyle::SP_FileDialogDetailedView));
    config_btn_->setIconSize(QSize(12, 12));
    config_btn_->setToolTip(tr("Configure widget"));
    config_btn_->setAccessibleName(tr("Configure widget"));
    config_btn_->setCursor(Qt::PointingHandCursor);
    config_btn_->setVisible(false);
    config_btn_->setStyleSheet(
        QString("QPushButton { color: %1; background: %2; border: 1px solid %3; border-radius: 2px; "
                "padding: 0px; }"
                "QPushButton:hover { color: %4; border-color: %4; background: %5; }")
            .arg(accent_color_, ui::colors::BG_SURFACE(), ui::colors::BORDER_DIM(), ui::colors::TEXT_PRIMARY(),
                 ui::colors::BG_HOVER()));
    connect(config_btn_, &QPushButton::clicked, this, &BaseWidget::on_config_clicked);
    hl->addWidget(config_btn_);

    // Refresh button
    refresh_btn_ = new QPushButton;
    refresh_btn_->setFixedSize(20, 20);
    refresh_btn_->setText("");
    refresh_btn_->setIcon(style()->standardIcon(QStyle::SP_BrowserReload));
    refresh_btn_->setIconSize(QSize(12, 12));
    refresh_btn_->setToolTip(tr("Refresh widget data"));
    refresh_btn_->setAccessibleName(tr("Refresh widget data"));
    refresh_btn_->setCursor(Qt::PointingHandCursor);
    refresh_btn_->setStyleSheet(
        QString("QPushButton { color: %1; background: %2; border: 1px solid %3; border-radius: 2px; "
                "padding: 0px; }"
                "QPushButton:hover { color: %4; border-color: %4; background: %5; }")
            .arg(accent_color_, ui::colors::BG_SURFACE(), ui::colors::BORDER_DIM(), ui::colors::TEXT_PRIMARY(),
                 ui::colors::BG_HOVER()));
    connect(refresh_btn_, &QPushButton::clicked, this, &BaseWidget::refresh_requested);
    hl->addWidget(refresh_btn_);

    // Close button
    auto* close_btn = new QPushButton;
    close_btn_ = close_btn;
    close_btn->setFixedSize(20, 20);
    close_btn->setText("");
    close_btn->setIcon(style()->standardIcon(QStyle::SP_TitleBarCloseButton));
    close_btn->setIconSize(QSize(11, 11));
    close_btn->setToolTip(tr("Close widget"));
    close_btn->setAccessibleName(tr("Close widget"));
    close_btn->setCursor(Qt::PointingHandCursor);
    close_btn->setStyleSheet(
        QString("QPushButton { color: %1; background: %2; border: 1px solid %3; border-radius: 2px; "
                "padding: 0px; }"
                "QPushButton:hover { color: %4; border-color: %4; background: %5; }")
            .arg(ui::colors::TEXT_TERTIARY(), ui::colors::BG_SURFACE(), ui::colors::BORDER_DIM(),
                 ui::colors::NEGATIVE(), ui::colors::BG_HOVER()));
    connect(close_btn, &QPushButton::clicked, this, &BaseWidget::close_requested);
    hl->addWidget(close_btn);

    vl->addWidget(title_bar_);

    // Auto-refresh styles on theme/font change — subclasses get this for free
    connect(&ui::ThemeManager::instance(), &ui::ThemeManager::theme_changed, this, [this](const ui::ThemeTokens&) {
        refresh_base_theme();
        on_theme_changed();
    });

    // ── Content ──
    content_ = new QWidget(this);
    content_->setStyleSheet("background: transparent;");
    content_layout_ = new QVBoxLayout(content_);
    content_layout_->setContentsMargins(0, 0, 0, 0);
    content_layout_->setSpacing(0);

    vl->addWidget(content_, 1);

    // ── Loading overlay ──
    // Lives as a child of the content area so it covers exactly the data
    // region (not the title bar). Sized via an event filter on `content_`.
    loading_overlay_ = new LoadingOverlay(content_);
    loading_overlay_->attach_to(content_);

    // ── Loading watchdog ──
    // Single-shot guard against producers that never publish. If the
    // overlay is still active 20 s after loading started with zero items
    // delivered, swap to a terminal "no data" overlay so the spinner can't
    // spin forever. Per-widget refresh still works to re-arm.
    //
    // 20 s (not 12 s) because dashboard restore fans out 20+ subscribe()
    // calls in one tick; producer-side rate limits then serialise the
    // cold-start fetches and tail widgets routinely take >12 s to see
    // their first payload even on healthy networks.
    loading_watchdog_ = new QTimer(this);
    loading_watchdog_->setSingleShot(true);
    loading_watchdog_->setInterval(20000);
    connect(loading_watchdog_, &QTimer::timeout, this, &BaseWidget::on_watchdog_fired);
}

void BaseWidget::refresh_base_theme() {
    QPalette pal = palette();
    pal.setColor(QPalette::Window, QColor(ui::colors::BG_SURFACE()));
    setPalette(pal);

    setStyleSheet(QString("#dashboardBaseWidget{background:%1;border:1px solid %2;border-radius:2px;}")
                      .arg(ui::colors::BG_SURFACE(), ui::colors::BORDER_BRIGHT()));
    if (title_bar_)
        title_bar_->setStyleSheet(QString("background:%1;border-bottom:1px solid %2;")
                                      .arg(ui::colors::BG_RAISED(), ui::colors::BORDER_DIM()));
    if (title_label_)
        title_label_->setStyleSheet(QString("color:%1;font-size:9px;font-weight:bold;letter-spacing:0.5px;"
                                            "background:transparent;text-transform:uppercase;")
                                        .arg(accent_color_));
    if (accent_bar_)
        accent_bar_->setStyleSheet(QString("background:%1;border-radius:1px;").arg(accent_color_));
    if (loading_label_)
        loading_label_->setStyleSheet(
            QString("color:%1;font-size:9px;background:transparent;").arg(ui::colors::AMBER()));
    if (refresh_btn_)
        refresh_btn_->setStyleSheet(
            QString("QPushButton{color:%1;background:%2;border:1px solid %3;border-radius:2px;padding:0;}"
                    "QPushButton:hover{color:%4;border-color:%4;background:%5;}")
                .arg(accent_color_, ui::colors::BG_SURFACE(), ui::colors::BORDER_DIM(), ui::colors::TEXT_PRIMARY(),
                     ui::colors::BG_HOVER()));
}

void BaseWidget::set_loading(bool loading) {
    loading_label_->setVisible(loading);
    loading_label_->setText(loading ? tr("LOADING...") : QString());
    if (!loading_overlay_)
        return;
    if (loading) {
        loading_overlay_->start_indeterminate();
        last_progress_loaded_ = 0;
        arm_watchdog();
    } else {
        loading_overlay_->finish();
        disarm_watchdog();
    }
}

void BaseWidget::set_loading_progress(int loaded, int expected) {
    if (!loading_overlay_)
        return;
    const bool active = (expected > 0) && (loaded < expected);
    loading_label_->setVisible(active);
    loading_label_->setText(active ? tr("LOADING...") : QString());
    loading_overlay_->set_progress(loaded, expected);
    last_progress_loaded_ = loaded;
    if (active)
        arm_watchdog();
    else
        disarm_watchdog();
}

void BaseWidget::set_error(const QString& error) {
    if (error.isEmpty())
        return;

    // Hide loading state — error overrides any in-flight progress.
    if (loading_overlay_)
        loading_overlay_->finish();
    if (loading_label_) {
        loading_label_->setVisible(false);
        loading_label_->setText(QString());
    }
    disarm_watchdog();

    // Clear content and show error
    QLayoutItem* item;
    while ((item = content_layout_->takeAt(0)) != nullptr) {
        if (item->widget())
            item->widget()->deleteLater();
        delete item;
    }

    auto* err_label = new QLabel(error);
    err_label->setAlignment(Qt::AlignCenter);
    err_label->setWordWrap(true);
    err_label->setStyleSheet(
        QString("color: %1; font-size: 9px; padding: 16px; background: transparent;").arg(ui::colors::NEGATIVE()));
    content_layout_->addWidget(err_label);
}

void BaseWidget::set_title(const QString& title) {
    title_label_->setText(title);
    // Screen readers announce the tile by its title; without this the whole
    // dashboard reads as a wall of unnamed frames.
    setAccessibleName(title);
    title_label_->setAccessibleName(title);
}

void BaseWidget::set_configurable(bool configurable) {
    if (config_btn_)
        config_btn_->setVisible(configurable);
}

void BaseWidget::schedule_render(std::function<void()> fn) {
    pending_render_ = std::move(fn);
    if (render_scheduled_)
        return;
    render_scheduled_ = true;
    // `this` as context object: Qt drops the pending call if the widget dies.
    QTimer::singleShot(0, this, [this]() {
        render_scheduled_ = false;
        // Move out first so a render that itself schedules another one
        // doesn't get clobbered.
        auto fn = std::move(pending_render_);
        pending_render_ = nullptr;
        if (fn)
            fn();
    });
}

void BaseWidget::on_config_clicked() {
    QDialog* dlg = make_config_dialog(this);
    if (!dlg)
        return;
    dlg->setAttribute(Qt::WA_DeleteOnClose);
    // Subclasses are expected to emit config_changed() inside the dialog's
    // accept flow; BaseWidget just opens the dialog modally.
    dlg->exec();
}

void BaseWidget::showEvent(QShowEvent* event) {
    QFrame::showEvent(event);
    // Subclasses commonly call set_loading(true) from their constructor,
    // which arms the watchdog before the widget is mounted. During
    // dashboard layout restore that gap is small but non-zero, and any
    // widget that's constructed but not immediately shown (collapsed
    // workspace, off-screen tile) would otherwise burn its 20 s budget
    // before the subscription is even dispatched. Restart the timer so
    // the budget begins when the user can actually see the spinner.
    if (loading_overlay_ && loading_overlay_->is_active() && last_progress_loaded_ == 0)
        arm_watchdog();
}

void BaseWidget::arm_watchdog() {
    if (!loading_watchdog_)
        return;
    loading_watchdog_->start();
}

void BaseWidget::disarm_watchdog() {
    if (!loading_watchdog_)
        return;
    loading_watchdog_->stop();
}

void BaseWidget::on_watchdog_fired() {
    if (!loading_overlay_ || !loading_overlay_->is_active())
        return;
    if (loading_label_) {
        loading_label_->setVisible(false);
        loading_label_->setText(QString());
    }
    // If some data trickled in before the timeout, just hide the spinner so
    // the partial content becomes visible. Otherwise surface a soft error so
    // the user knows the widget hasn't simply hung.
    if (last_progress_loaded_ > 0) {
        loading_overlay_->finish();
    } else {
        loading_overlay_->set_error(tr("No data yet — click refresh to retry"));
    }
}

void BaseWidget::changeEvent(QEvent* event) {
    if (event->type() == QEvent::LanguageChange)
        retranslateUi();
    QFrame::changeEvent(event);
}

void BaseWidget::retranslateUi() {
    if (config_btn_) {
        config_btn_->setToolTip(tr("Configure widget"));
        config_btn_->setAccessibleName(tr("Configure widget"));
    }
    if (refresh_btn_) {
        refresh_btn_->setToolTip(tr("Refresh widget data"));
        refresh_btn_->setAccessibleName(tr("Refresh widget data"));
    }
    if (close_btn_) {
        close_btn_->setToolTip(tr("Close widget"));
        close_btn_->setAccessibleName(tr("Close widget"));
    }
    if (loading_label_ && loading_label_->isVisible())
        loading_label_->setText(tr("LOADING..."));
}

} // namespace fincept::screens::widgets
