#include "CloseGesture.hpp"

#include "../../../../Compositor.hpp"
#include "../../../../managers/animation/DesktopAnimationManager.hpp"
#include "../../../../render/Renderer.hpp"
#include "../../../../managers/eventLoop/EventLoopManager.hpp"
#include "../../../../managers/eventLoop/EventLoopTimer.hpp"
#include "../../../../config/ConfigValue.hpp"
#include "../../../../desktop/state/FocusState.hpp"
#include "../../../../layout/target/Target.hpp"

using namespace Desktop::View;

constexpr const float                   MAX_DISTANCE = 200.F;

static std::vector<SP<CEventLoopTimer>> trackpadCloseTimers;

//
static Vector2D lerpVal(const Vector2D& from, const Vector2D& to, const float& t) {
    return Vector2D{
        from.x + ((to.x - from.x) * t),
        from.y + ((to.y - from.y) * t),
    };
}

static float lerpVal(const float& from, const float& to, const float& t) {
    return from + ((to - from) * t);
}

void CCloseTrackpadGesture::begin(const ITrackpadGesture::STrackpadGestureBegin& e) {
    ITrackpadGesture::begin(e);

    m_window = Desktop::focusState()->window();

    if (!m_window)
        return;

    m_alphaFrom = m_window->alphaGoal(WINDOW_ALPHA_FADE);
    m_posFrom   = m_window->m_realPosition->goal();
    m_sizeFrom  = m_window->m_realSize->goal();

    g_pDesktopAnimationManager->startAnimation(m_window.lock(), CDesktopAnimationManager::ANIMATION_TYPE_OUT, true);
    *m_window->alpha(WINDOW_ALPHA_FADE) = 0.f;

    m_alphaTo = m_window->alphaGoal(WINDOW_ALPHA_FADE);
    m_posTo   = m_window->m_realPosition->goal();
    m_sizeTo  = m_window->m_realSize->goal();

    m_window->alpha(WINDOW_ALPHA_FADE)->setValueAndWarp(m_alphaFrom);
    m_window->m_realPosition->setValueAndWarp(m_posFrom);
    m_window->m_realSize->setValueAndWarp(m_sizeFrom);

    m_lastDelta = 0.F;
}

void CCloseTrackpadGesture::update(const ITrackpadGesture::STrackpadGestureUpdate& e) {
    if (!m_window)
        return;

    g_pHyprRenderer->damageWindow(m_window.lock());

    m_lastDelta += distance(e);

    const auto FADEPERCENT = std::clamp(m_lastDelta / MAX_DISTANCE, 0.F, 1.F);

    m_window->alpha(WINDOW_ALPHA_FADE)->setValueAndWarp(lerpVal(m_alphaFrom, m_alphaTo, FADEPERCENT));
    m_window->m_realPosition->setValueAndWarp(lerpVal(m_posFrom, m_posTo, FADEPERCENT));
    m_window->m_realSize->setValueAndWarp(lerpVal(m_sizeFrom, m_sizeTo, FADEPERCENT));

    g_pDecorationPositioner->onWindowUpdate(m_window.lock());

    g_pHyprRenderer->damageWindow(m_window.lock());
}

void CCloseTrackpadGesture::end(const ITrackpadGesture::STrackpadGestureEnd& e) {
    static const auto PTIMEOUT = CConfigValue<Config::INTEGER>("gestures:close_max_timeout");

    if (!m_window)
        return;

    const auto COMPLETION = std::clamp(m_lastDelta / MAX_DISTANCE, 0.F, 1.F);

    if (COMPLETION < 0.2F) {
        // revert the animation
        g_pHyprRenderer->damageWindow(m_window.lock());
        *m_window->alpha(WINDOW_ALPHA_FADE) = m_alphaFrom;
        *m_window->m_realPosition           = m_posFrom;
        *m_window->m_realSize               = m_sizeFrom;
        return;
    }

    // commence. Close the window and restore our current state to avoid a harsh anim
    const auto CURRENT_ALPHA = m_window->alphaValue(WINDOW_ALPHA_FADE);
    const auto CURRENT_POS   = m_window->m_realPosition->value();
    const auto CURRENT_SIZE  = m_window->m_realSize->value();

    Desktop::focusState()->window()->sendClose();

    m_window->alpha(WINDOW_ALPHA_FADE)->setValueAndWarp(CURRENT_ALPHA);
    m_window->m_realPosition->setValueAndWarp(CURRENT_POS);
    m_window->m_realSize->setValueAndWarp(CURRENT_SIZE);

    // this is a kinda hack, but oh well.
    m_window->m_realPosition->setCallbackOnBegin(
        [CURRENT_POS, window = m_window](auto) {
            if (!window || !window->m_isMapped)
                return;

            window->m_realPosition->setValueAndWarp(CURRENT_POS);
        },
        false);

    m_window->m_realSize->setCallbackOnBegin(
        [CURRENT_SIZE, window = m_window](auto) {
            if (!window || !window->m_isMapped)
                return;

            window->m_realSize->setValueAndWarp(CURRENT_SIZE);
        },
        false);

    // we give windows 2s to close. If they don't, pop them back in.
    auto timer = makeShared<CEventLoopTimer>(
        std::chrono::milliseconds(*PTIMEOUT),
        [window = m_window](SP<CEventLoopTimer> self, void* data) {
            std::erase(trackpadCloseTimers, self);

            // if after 2 seconds the window is still alive and mapped, we revert our changes.
            if (!window)
                return;

            window->m_realPosition->setCallbackOnBegin(nullptr);
            window->m_realSize->setCallbackOnBegin(nullptr);

            if (!window->m_isMapped)
                return;

            window->layoutTarget()->recalc();
            window->updateDecorationValues();
            window->sendWindowSize(true);
            *window->alpha(WINDOW_ALPHA_FADE) = 1.F;
        },
        nullptr);
    trackpadCloseTimers.emplace_back(timer);
    g_pEventLoopManager->addTimer(timer);

    m_window.reset();
}
