#include "slidingstackedwidget.h"
#include <QWidget>
#include <QLabel>
#include <QScreen>
#include "../../qEmbyCore/config/configstore.h"
#include "config/config_keys.h"
#include <cmath>

namespace {
QPoint interpolatePoint(const QPoint &start, const QPoint &end, qreal progress)
{
    return QPoint(
        qRound(start.x() + (end.x() - start.x()) * progress),
        qRound(start.y() + (end.y() - start.y()) * progress));
}
}

SlidingStackedWidget::SlidingStackedWidget(QWidget *parent)
    : QStackedWidget(parent),
    m_speed(350), 
    m_animationType(QEasingCurve::OutCubic), 
    m_isAnimating(false),
    m_nextIndex(0)
{
    m_slideTimer.setTimerType(Qt::PreciseTimer);
    connect(&m_slideTimer, &QTimer::timeout, this,
            &SlidingStackedWidget::updateSlideAnimation);
}

void SlidingStackedWidget::setSpeed(int speed)
{
    m_speed = speed;
}

void SlidingStackedWidget::setEasingCurve(QEasingCurve::Type curveType)
{
    m_animationType = curveType;
}

void SlidingStackedWidget::slideInWgt(QWidget *widget, SlideDirection direction)
{
    int idx = indexOf(widget);
    if (idx != -1) {
        slideInIdx(idx, direction);
    }
}

void SlidingStackedWidget::slideInIdx(int idx, SlideDirection direction)
{
    if (idx < 0 || idx >= count())
        return;

    
    if (currentIndex() == idx && !m_isAnimating)
        return;

    
    
    if (m_isAnimating) {
        m_slideTimer.stop();
        animationDoneSlot();           
    }

    
    bool reduceAnimations = ConfigStore::instance()->get<bool>(ConfigKeys::UiAnimations, false);
    if (reduceAnimations) {
        m_nextIndex = idx;
        setCurrentIndex(idx);
        QWidget *w = widget(idx);
        if (w) w->raise();
        flushPendingWidgetDisposals();
        Q_EMIT animationFinished();
        return;
    }

    m_isAnimating = true;
    m_nextIndex = idx;

    int now = currentIndex();
    QWidget *widgetNow = widget(now);
    QWidget *widgetNext = widget(idx);

    
    if (!widgetNow || !widgetNext) {
        m_isAnimating = false;
        if (widgetNext) {
            setCurrentIndex(idx);
        }
        return;
    }

    int width = this->width();
    int height = this->height();

    int offsetX = 0;
    int offsetY = 0;

    SlideDirection actualDirection = direction;
    if (direction == Automatic) {
        if (now < idx)
            actualDirection = RightToLeft;
        else
            actualDirection = LeftToRight;
    }

    if (actualDirection == RightToLeft) {
        offsetX = width;
        offsetY = 0;
    } else if (actualDirection == LeftToRight) {
        offsetX = -width;
        offsetY = 0;
    } else if (actualDirection == BottomToTop) {
        offsetX = 0;
        offsetY = height;
    } else if (actualDirection == TopToBottom) {
        offsetX = 0;
        offsetY = -height;
    }

    
    widgetNext->setGeometry(0, 0, width, height);

    QPoint pNext = widgetNext->pos();
    QPoint pNow = widgetNow->pos();

    const bool useSnapshot =
        ConfigStore::instance()->get<bool>(ConfigKeys::SnapshotNavigation, true);

    QWidget *animTargetNow = widgetNow;
    QWidget *animTargetNext = widgetNext;
    bool usingSnapshotAnimation = false;

    if (useSnapshot) {
        clearSnapshotLabels();

        m_currentSnapshotLabel =
            createSnapshotLabel(widgetNow, QRect(pNow, QSize(width, height)));

        widgetNext->show();
        widgetNext->raise();
        m_nextSnapshotLabel =
            createSnapshotLabel(widgetNext,
                                QRect(QPoint(pNext.x() + offsetX,
                                             pNext.y() + offsetY),
                                      QSize(width, height)));

        usingSnapshotAnimation =
            m_currentSnapshotLabel && m_nextSnapshotLabel;
    }

    if (usingSnapshotAnimation) {
        animTargetNow = m_currentSnapshotLabel;
        animTargetNext = m_nextSnapshotLabel;
        widgetNow->hide();
        widgetNext->hide();
    } else {
        clearSnapshotLabels();

        widgetNext->move(pNext.x() + offsetX, pNext.y() + offsetY);
        widgetNext->show();
        widgetNext->raise();
    }

    startSlideAnimation(animTargetNow,
                        pNow,
                        QPoint(pNow.x() - offsetX, pNow.y() - offsetY),
                        animTargetNext,
                        QPoint(pNext.x() + offsetX, pNext.y() + offsetY),
                        pNext);
}

void SlidingStackedWidget::disposeWidgetWhenSafe(QWidget *widget)
{
    if (!widget)
    {
        return;
    }

    const QPointer<QWidget> safeWidget(widget);
    if (!m_pendingWidgetDisposals.contains(safeWidget))
    {
        m_pendingWidgetDisposals.append(safeWidget);
    }

    if (!m_isAnimating)
    {
        flushPendingWidgetDisposals();
    }
}

QLabel *SlidingStackedWidget::createSnapshotLabel(QWidget *source,
                                                 const QRect &geometry)
{
    if (!source || geometry.size().isEmpty())
    {
        return nullptr;
    }

    QPixmap snapshot = source->grab(QRect(QPoint(0, 0), geometry.size()));
    if (snapshot.isNull())
    {
        return nullptr;
    }

    auto *label = new QLabel(this);
    label->setAttribute(Qt::WA_TransparentForMouseEvents, true);
    label->setPixmap(snapshot);
    label->setGeometry(geometry);
    label->show();
    label->raise();
    return label;
}

void SlidingStackedWidget::clearSnapshotLabels()
{
    if (m_currentSnapshotLabel)
    {
        m_currentSnapshotLabel->hide();
        m_currentSnapshotLabel->deleteLater();
        m_currentSnapshotLabel = nullptr;
    }

    if (m_nextSnapshotLabel)
    {
        m_nextSnapshotLabel->hide();
        m_nextSnapshotLabel->deleteLater();
        m_nextSnapshotLabel = nullptr;
    }
}

void SlidingStackedWidget::startSlideAnimation(QWidget *widgetNow,
                                               const QPoint &nowStart,
                                               const QPoint &nowEnd,
                                               QWidget *widgetNext,
                                               const QPoint &nextStart,
                                               const QPoint &nextEnd)
{
    m_animWidgetNow = widgetNow;
    m_animWidgetNext = widgetNext;
    m_animNowStart = nowStart;
    m_animNowEnd = nowEnd;
    m_animNextStart = nextStart;
    m_animNextEnd = nextEnd;

    if (!m_animWidgetNow || !m_animWidgetNext || m_speed <= 0)
    {
        animationDoneSlot();
        return;
    }

    m_slideTimer.setInterval(animationFrameIntervalMs());
    m_slideClock.restart();
    updateSlideAnimation();
    m_slideTimer.start();
}

void SlidingStackedWidget::updateSlideAnimation()
{
    if (!m_animWidgetNow || !m_animWidgetNext)
    {
        animationDoneSlot();
        return;
    }

    const qreal elapsedMs = m_slideClock.isValid()
                                ? m_slideClock.nsecsElapsed() / 1000000.0
                                : static_cast<qreal>(m_speed);
    const qreal linearProgress =
        qBound<qreal>(0.0, elapsedMs / qMax(1, m_speed), 1.0);
    const qreal easedProgress =
        QEasingCurve(m_animationType).valueForProgress(linearProgress);

    m_animWidgetNow->move(
        interpolatePoint(m_animNowStart, m_animNowEnd, easedProgress));
    m_animWidgetNext->move(
        interpolatePoint(m_animNextStart, m_animNextEnd, easedProgress));

    if (linearProgress >= 1.0)
    {
        animationDoneSlot();
    }
}

int SlidingStackedWidget::animationFrameIntervalMs() const
{
    const QScreen *targetScreen = screen();
    if (!targetScreen && window())
    {
        targetScreen = window()->screen();
    }

    const qreal refreshRate = targetScreen ? targetScreen->refreshRate() : 0.0;
    if (refreshRate <= 0.0)
    {
        return 16;
    }

    return qBound(4,
                  static_cast<int>(std::lround(1000.0 / refreshRate)),
                  16);
}

void SlidingStackedWidget::animationDoneSlot()
{
    m_slideTimer.stop();
    m_animWidgetNow.clear();
    m_animWidgetNext.clear();
    clearSnapshotLabels();

    
    if (m_nextIndex >= 0 && m_nextIndex < count()) {
        setCurrentIndex(m_nextIndex);
        QWidget *w = widget(m_nextIndex);
        if (w) {
            w->raise();
        }
    }
    m_isAnimating = false;
    flushPendingWidgetDisposals();
    Q_EMIT animationFinished();
}

void SlidingStackedWidget::flushPendingWidgetDisposals()
{
    if (m_isAnimating || m_pendingWidgetDisposals.isEmpty())
    {
        return;
    }

    const auto widgetsToDispose = m_pendingWidgetDisposals;
    m_pendingWidgetDisposals.clear();

    for (const QPointer<QWidget> &widget : widgetsToDispose)
    {
        if (!widget)
        {
            continue;
        }

        if (indexOf(widget) != -1)
        {
            removeWidget(widget);
        }
        widget->deleteLater();
    }
}
