/*
    SPDX-FileCopyrightText: 2025 Vlad Zahorodnii <vlad.zahorodnii@kde.org>

    SPDX-License-Identifier: LGPL-2.0-or-later
*/

#include "lookandfeelautoswitcher.h"
#include "idletimeout.h"
#include "klookandfeelmanager.h"
#include "logging.h"
#include "lookandfeelautoswitcherstate.h"
#include "lookandfeelsettings.h"

#include <KPackage/PackageLoader>
#include <KPluginFactory>

#include <QDateTime>
#include <QDebug>
#include <algorithm>

K_PLUGIN_CLASS_WITH_JSON(LookAndFeelAutoSwitcher, "lookandfeelautoswitcher.json")

using namespace std::chrono_literals;

LookAndFeelAutoSwitcher::LookAndFeelAutoSwitcher(QObject *parent, const QList<QVariant> &)
    : KDEDModule(parent)
    , m_settings(std::make_unique<LookAndFeelSettings>())
    , m_state(std::make_unique<LookAndFeelAutoSwitcherState>())
    , m_configWatcher(KConfigWatcher::create(m_settings->sharedConfig()))
{
    connect(m_configWatcher.get(), &KConfigWatcher::configChanged, this, &LookAndFeelAutoSwitcher::onConfigChanged);

    reconfigure();
}

LookAndFeelAutoSwitcher::~LookAndFeelAutoSwitcher() = default;

bool LookAndFeelAutoSwitcher::changesConfig(const KConfigGroup &group, const QByteArrayList &names) const
{
    if (group.name() == QLatin1String("KDE")) {
        const QByteArrayList keys{
            QByteArrayLiteral("AutomaticLookAndFeel"),
            QByteArrayLiteral("DefaultLightLookAndFeel"),
            QByteArrayLiteral("DefaultDarkLookAndFeel"),
        };
        return std::ranges::any_of(keys, [names](const QByteArray &name) {
            return names.contains(name);
        });
    }

    return false;
}

void LookAndFeelAutoSwitcher::onConfigChanged(const KConfigGroup &group, const QByteArrayList &names)
{
    m_settings->read();
    if (changesConfig(group, names)) {
        reconfigure();
    }
}

void LookAndFeelAutoSwitcher::reconfigure()
{
    if (!m_settings->automaticLookAndFeel()) {
        qCDebug(LOOKANDFEELAUTOSWITCHER) << "Disabling automatic theme switching";
        m_scheduleTimer.reset();
        m_scheduleProvider.reset();
        m_skewMonitor.reset();
    } else {
        qCDebug(LOOKANDFEELAUTOSWITCHER) << "Enabling automatic theme switching";

        if (!m_skewMonitor) {
            m_skewMonitor = std::make_unique<KSystemClockSkewNotifier>();
            connect(m_skewMonitor.get(), &KSystemClockSkewNotifier::skewed, this, &LookAndFeelAutoSwitcher::rescheduleAndUpdateNow);
            m_skewMonitor->setActive(true);
        }

        if (!m_scheduleTimer) {
            m_scheduleTimer = std::make_unique<QTimer>();
            m_scheduleTimer->setSingleShot(true);
            connect(m_scheduleTimer.get(), &QTimer::timeout, this, &LookAndFeelAutoSwitcher::rescheduleAndUpdateAuto);
        }

        if (!m_scheduleProvider) {
            m_scheduleProvider = std::make_unique<KDarkLightScheduleProvider>(m_state->serializedSchedule());
            connect(m_scheduleProvider.get(), &KDarkLightScheduleProvider::scheduleChanged, this, [this]() {
                m_state->setSerializedSchedule(m_scheduleProvider->state());
                m_state->save();

                rescheduleAndUpdateAuto();
            });
        }

        rescheduleAndUpdateNow();
    }
}

static QDateTime transitionBreakPoint(const KDarkLightTransition &transition)
{
    return transition.startDateTime() + (transition.endDateTime() - transition.startDateTime()) / 2;
}

QString LookAndFeelAutoSwitcher::lookAndFeelAtDateTime(const QDateTime &dateTime) const
{
    if (transitionBreakPoint(*m_previousOrCurrentTransition) - dateTime > 5s) {
        if (m_previousOrCurrentTransition->type() == KDarkLightTransition::Morning) {
            return m_settings->defaultDarkLookAndFeel();
        } else {
            return m_settings->defaultLightLookAndFeel();
        }
    } else {
        // We are past or at the transition break point.
        if (m_previousOrCurrentTransition->type() == KDarkLightTransition::Morning) {
            return m_settings->defaultLightLookAndFeel();
        } else {
            return m_settings->defaultDarkLookAndFeel();
        }
    }
}

void LookAndFeelAutoSwitcher::applyLookAndFeel(const QString &id)
{
    qCDebug(LOOKANDFEELAUTOSWITCHER) << "Applying" << id << "global theme";

    if (m_settings->lookAndFeelPackage() == id) {
        return;
    }

    m_settings->setLookAndFeelPackage(id);
    m_settings->save();

    KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LookAndFeel"));
    package.setPath(id);

    const KLookAndFeelManager::Contents selection = KLookAndFeelManager::AppearanceSettings;

    KLookAndFeelManager manager;
    manager.save(package, selection);
}

void LookAndFeelAutoSwitcher::reschedule()
{
    const QDateTime now = QDateTime::currentDateTime();

    const KDarkLightSchedule schedule = m_scheduleProvider->schedule();
    m_previousOrCurrentTransition = schedule.previousTransition(now);

    QDateTime rescheduleDateTime = transitionBreakPoint(*m_previousOrCurrentTransition);
    if (rescheduleDateTime - now <= 5s) {
        rescheduleDateTime = transitionBreakPoint(*schedule.nextTransition(now));
    }

    m_scheduleTimer->start(rescheduleDateTime - now);

    qCDebug(LOOKANDFEELAUTOSWITCHER) << "Next transition will occur at" << rescheduleDateTime << "in" << (rescheduleDateTime - now).count() << "milliseconds";
}

void LookAndFeelAutoSwitcher::rescheduleAndUpdateAuto()
{
    if (m_settings->automaticLookAndFeelOnIdle()) {
        rescheduleAndUpdateIdle();
    } else {
        rescheduleAndUpdateNow();
    }
}

void LookAndFeelAutoSwitcher::rescheduleAndUpdateNow()
{
    reschedule();
    updateNow();
}

void LookAndFeelAutoSwitcher::rescheduleAndUpdateIdle()
{
    reschedule();
    updateIdle();
}

void LookAndFeelAutoSwitcher::updateNow()
{
    qCDebug(LOOKANDFEELAUTOSWITCHER) << "Attempting to update the global theme now";
    m_idleTimeout.reset();
    applyLookAndFeel(lookAndFeelAtDateTime(QDateTime::currentDateTime()));
}

void LookAndFeelAutoSwitcher::updateIdle()
{
    qCDebug(LOOKANDFEELAUTOSWITCHER) << "Attempting to update the global theme on idleness";
    const std::chrono::seconds idleInterval(m_settings->automaticLookAndFeelIdleInterval());
    m_idleTimeout = std::make_unique<IdleTimeout>(idleInterval);
    connect(m_idleTimeout.get(), &IdleTimeout::timeout, this, [this]() {
        applyLookAndFeel(lookAndFeelAtDateTime(QDateTime::currentDateTime()));
        m_idleTimeout.reset();
    });
}

#include "lookandfeelautoswitcher.moc"
#include "moc_lookandfeelautoswitcher.cpp"
