/*
Crafter®.Graphics
Copyright (C) 2026 Catcrafts®
catcrafts.net

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License version 3.0 as published by the Free Software Foundation;

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
*/

// Regression test for issue #32: the Wayland wl_pointer.axis handler used to
// be an empty stub, so Window::onMouseScroll (and with it MouseScrollBind)
// never fired on native Linux. This drives Device::PointerListenerHandleAxis
// directly — no compositor needed; a default-constructed Window is inert on
// Wayland — and asserts the normalization contract documented on
// Window::onMouseScroll:
//
//   - one wheel detent (15 axis units, the libinput convention) → ±1,
//     positive = wheel down, sign packed into the uint32 payload
//   - sub-detent smooth-scroll values accumulate across events instead of
//     each truncating to zero
//   - horizontal axis events are ignored
//   - no focused window → no event (and no crash)
//   - the sub-detent remainder is dropped on pointer leave
//
// The Win32 WM_MOUSEWHEEL path added by the same issue follows the identical
// contract but can't be exercised from a Linux test runner.

#include <wayland-client.h>
#include <cstdlib>

import Crafter.Graphics;
import Crafter.Event;
import std;
using namespace Crafter;

namespace {

int failures = 0;

void Check(bool ok, std::string_view what) {
    std::println("{}  {}", ok ? "PASS" : "FAIL", what);
    if (!ok) ++failures;
}

// Convenience: feed one vertical (or horizontal) axis event, in axis units.
void SendAxis(double axisUnits, std::uint32_t axis = WL_POINTER_AXIS_VERTICAL_SCROLL) {
    Device::PointerListenerHandleAxis(nullptr, nullptr, /*time=*/0, axis,
                                      wl_fixed_from_double(axisUnits));
}

} // namespace

int main() {
    Window window;  // default ctor creates no surface — safe without a compositor
    std::vector<std::int32_t> deltas;
    EventListener<std::uint32_t> sub(&window.onMouseScroll,
        [&](std::uint32_t delta) {
            // Same reinterpretation consumers (Input::Map) use.
            deltas.push_back(static_cast<std::int32_t>(delta));
        });

    Device::focusedWindow = &window;

    // One detent down / one detent up.
    SendAxis(15.0);
    Check(deltas == std::vector<std::int32_t>{1}, "one detent down → +1");
    deltas.clear();
    SendAxis(-15.0);
    Check(deltas == std::vector<std::int32_t>{-1}, "one detent up → -1");
    deltas.clear();

    // Smooth scroll: five 3-unit events must add up to exactly one detent,
    // delivered on the event that completes it.
    for (int i = 0; i < 4; ++i) SendAxis(3.0);
    Check(deltas.empty(), "four 3-unit events → nothing yet");
    SendAxis(3.0);
    Check(deltas == std::vector<std::int32_t>{1}, "fifth 3-unit event completes the detent → +1");
    deltas.clear();

    // Direction reversal mid-accumulation must not misfire.
    SendAxis(7.5);
    SendAxis(-7.5);
    SendAxis(-15.0);
    Check(deltas == std::vector<std::int32_t>{-1}, "reversal cancels remainder, full detent up → -1");
    deltas.clear();

    // A big flick delivers multiple detents in one event.
    SendAxis(45.0);
    Check(deltas == std::vector<std::int32_t>{3}, "45 axis units in one event → +3");
    deltas.clear();

    // Horizontal scroll has no Window event — must be ignored entirely
    // (including the accumulator).
    SendAxis(15.0, WL_POINTER_AXIS_HORIZONTAL_SCROLL);
    Check(deltas.empty(), "horizontal axis ignored");
    SendAxis(14.0);
    Check(deltas.empty(), "horizontal units did not leak into the vertical accumulator");
    SendAxis(1.0);
    Check(deltas == std::vector<std::int32_t>{1}, "vertical accumulator unaffected by horizontal events");
    deltas.clear();

    // Pointer leave drops the sub-detent remainder.
    SendAxis(14.0);
    Device::PointerListenerHandleLeave(nullptr, nullptr, 0, nullptr);
    Check(Device::focusedWindow == nullptr, "leave clears the focused window");
    SendAxis(14.0);  // no focused window — must be a no-op, not a crash
    Check(deltas.empty(), "no focused window → no event");
    Device::focusedWindow = &window;
    SendAxis(14.0);
    Check(deltas.empty(), "remainder was reset on leave (14 + 14 stayed < a detent)");
    SendAxis(1.0);
    Check(deltas == std::vector<std::int32_t>{1}, "fresh accumulation completes normally");

    Device::focusedWindow = nullptr;
    if (failures != 0) {
        std::println("{} check(s) failed", failures);
        return EXIT_FAILURE;
    }
    std::println("all checks passed");
    return EXIT_SUCCESS;
}
