// SPDX-FileCopyrightText: 2023 - 2026 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: GPL-3.0-or-later

#include "appearance.h"

#include <QGuiApplication>
#include <QScreen>
#include <QImage>
#include <QLoggingCategory>
#include <QCursor>

#include "Appearance1.h"
#include "blurhash.hpp"
#include "../launchercontroller.h"
#include "../desktopintegration.h"

Q_LOGGING_CATEGORY(logDdeIntegration, "org.deepin.dde.launchpad.integration")

using Appearance1 = __Appearance1;

Appearance::Appearance(QObject *parent)
    : QObject(parent)
    , m_dbusAppearanceIface(new Appearance1(Appearance1::staticInterfaceName(), "/org/deepin/dde/Appearance1",
                                            QDBusConnection::sessionBus(), this))
    , m_wallpaperBlurhash("L35?hb%#0ADeorNFVuy501Me?*%o")
{
    QTimer::singleShot(0, this, &Appearance::updateAllWallpaper);

    connect(m_dbusAppearanceIface, &Appearance1::Changed, this, [this](const QString & key, const QString &) {
        if (key == "allwallpaperuris") updateAllWallpaper();
    });

    connect(&(LauncherController::instance()), &LauncherController::currentFrameChanged,
            this, &Appearance::updateCurrentWallpaperBlurhash);
    // Use a delayed call to wait for screen info to be ready after visible changes
    connect(&(LauncherController::instance()), &LauncherController::visibleChanged,
            this, &Appearance::updateCurrentWallpaperBlurhash);
    connect(&(LauncherController::instance()), &LauncherController::currentScreenChanged,
            this, &Appearance::updateCurrentWallpaperBlurhash);

    if (m_dbusAppearanceIface->isValid()) {
        connect(m_dbusAppearanceIface, &Appearance1::OpacityChanged, this, [this](double value) {
            qCDebug(logDdeIntegration) << "Opacity changed via DBus:" << value;
            setOpacity(value);
        });
        setOpacity(m_dbusAppearanceIface->opacity());
    }
}

Appearance::~Appearance()
{

}

QString Appearance::wallpaperBlurhash() const
{
    return m_wallpaperBlurhash;
}

void Appearance::updateCurrentWallpaperBlurhash()
{
    if (!LauncherController::instance().visible() || !LauncherController::instance().isFullScreenFrame()) {
        qCDebug(logDdeIntegration) << "Launcher not visible or not fullscreen, skipping blurhash update";
        return;
    }

    // TODO: LauncherController.currentScreen is sometimes empty when read here (QML sets it from dock later),
    // so we fallback to dock geometry / cursor / primary. In practice we always hit dockGeometry; currentScreen
    // and dock are the same logical source. Could simplify to use only dock geometry first if desired.
    auto screenByName = [](const QString &name) -> QScreen * {
        if (name.isEmpty())
            return nullptr;
        for (QScreen *s : qApp->screens()) {
            if (s && s->name() == name)
                return s;
        }
        return nullptr;
    };
    QString requestedScreenName = LauncherController::instance().currentScreen();
    QScreen *targetScreen = screenByName(requestedScreenName);
    if (!targetScreen) {
        QRect dockGeometry = DesktopIntegration::instance().dockGeometry();
        if (dockGeometry.isValid() && dockGeometry.width() > 0 && dockGeometry.height() > 0)
            targetScreen = qApp->screenAt(dockGeometry.center());
    }
    if (!targetScreen)
        targetScreen = qApp->screenAt(QCursor::pos());
    if (!targetScreen)
        targetScreen = QGuiApplication::primaryScreen();
    QString screenName = targetScreen ? targetScreen->name() : QString();
    if (screenName.isEmpty() || !m_dbusAppearanceIface->isValid())
        return;
    qCDebug(logDdeIntegration) << "Getting wallpaper for screen:" << screenName;
    QDBusPendingReply<QString> async = m_dbusAppearanceIface->GetCurrentWorkspaceBackgroundForMonitor(screenName);
    QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(async, this);
    connect(watcher, &QDBusPendingCallWatcher::finished, this, [this](QDBusPendingCallWatcher* call){
        QDBusPendingReply<QString> reply = *call;
        if (reply.isError()) {
            qCWarning(logDdeIntegration) << "Cannot get wallpaper from dbus:" << reply.error();
        } else {
            QUrl wallpaperUrl(reply.value());
            qCDebug(logDdeIntegration) << "Got wallpaper URL from dbus:" << wallpaperUrl;
            
            if (m_wallpaperBlurMap.contains(wallpaperUrl)) {
                QString newBlurhash = m_wallpaperBlurMap.value(wallpaperUrl);
                if (m_wallpaperBlurhash != newBlurhash) {
                    m_wallpaperBlurhash = newBlurhash;
                    emit wallpaperBlurhashChanged();
                }
            } else {
                qCDebug(logDdeIntegration) << "No cached blurhash found, updating all wallpapers";
                // try update new workspace background image
                updateAllWallpaper();
            }
        }
        call->deleteLater();
    });
}

void Appearance::updateAllWallpaper()
{
    QJsonParseError err;
    QString urls = m_dbusAppearanceIface->wallpaperURls();
    QJsonDocument doc = QJsonDocument::fromJson(urls.toUtf8(), &err);
    if (err.error != QJsonParseError::NoError) {
        qCWarning(logDdeIntegration) << "Get wallpapers failed:" << err.errorString();
        return;
    }

    if (!doc.isObject()) {
        qCWarning(logDdeIntegration) << "Wallpaper document is not a JSON object";
        return;
    }

    QJsonObject jsonObj = doc.object();
    
    // Iterate through all keys in the JSON object to get wallpapers for all screens
    // Keys are in format "ScreenName&&WorkspaceIndex" (e.g., "eDP-1&&1", "HDMI-1&&1")
    for (auto it = jsonObj.begin(); it != jsonObj.end(); ++it) {
        QJsonValue v = it.value();
        if (!v.isString()) {
            continue;
        }

        QUrl wallpaperUrl(v.toString());
        if (m_wallpaperBlurMap.contains(wallpaperUrl))
            continue;

        QFuture<QString> blurhashFuture = QtConcurrent::run([wallpaperUrl](){
            QImage image;
            if (image.load(wallpaperUrl.toLocalFile())) {
                image.convertTo(QImage::Format_RGB888);
                std::string blurhash = blurhash::encode(image.constBits(), image.width(), image.height(), 4, 3);
                QString newBlurhash(QString::fromStdString(blurhash));
                return newBlurhash;
            } else {
                return QString();
            }
        });

        QFutureWatcher<QString> *watcher = new QFutureWatcher<QString>;
        watcher->setFuture(blurhashFuture);
        connect(watcher, &QFutureWatcher<QString>::finished, this, [this, wallpaperUrl](){
            auto watcher = static_cast<QFutureWatcher<QString>*>(sender());
            if (!watcher)
                return;

            QString result(watcher->result());
            if (!result.isEmpty() && !m_wallpaperBlurMap.contains(wallpaperUrl)) {
                m_wallpaperBlurMap[wallpaperUrl] = result;
                updateCurrentWallpaperBlurhash();
            }

            watcher->deleteLater();
            m_blurhashWatchers.removeOne(watcher);
        });

        m_blurhashWatchers << watcher;
    }
}

qreal Appearance::opacity() const
{
    return m_opacity;
}

void Appearance::setOpacity(qreal newOpacity)
{
    if (qFuzzyCompare(m_opacity, newOpacity)) {
        qCDebug(logDdeIntegration) << "Opacity unchanged, skipping";
        return;
    }
    m_opacity = newOpacity;
    qCInfo(logDdeIntegration) << "Opacity changed to:" << newOpacity;
    emit opacityChanged();
}

double Appearance::scaleFactor() const
{
    if (!m_dbusAppearanceIface || !m_dbusAppearanceIface->isValid()) {
        qCWarning(logDdeIntegration) << "DBus interface invalid, returning default scale 1.0";
        return 1.0; // 默认返回1.0（100%缩放）
    }
    return m_dbusAppearanceIface->GetScaleFactor();
}
