#include <keybinds/Bind.hpp>

#include <gtest/gtest.h>

#include <array>

using namespace Keybinds;
using namespace Input;

static SResolvedKey resolvedKey(const char* name, xkb_keycode_t code, std::optional<Input::eKeyboardModifiers> modifier = std::nullopt) {
    return {
        .sym      = xkb_keysym_from_name(name, XKB_KEYSYM_CASE_INSENSITIVE),
        .code     = code,
        .modifier = modifier,
    };
}

static std::expected<CBind, std::string> makeBind(std::vector<std::string>&& keys, eBindFlags flags = sc<eBindFlags>(0)) {
    return CBind::make(std::move(keys), flags, [] { return SBindResult{}; });
}

TEST(Keybinds, SidedModifierMatchesCorrectSide) {
    auto result = makeBind({"SHIFT_L", "K"});
    ASSERT_TRUE(result.has_value());

    const auto       LEFT_SHIFT  = resolvedKey("SHIFT_L", 50, HL_MODIFIER_SHIFT);
    const auto       RIGHT_SHIFT = resolvedKey("SHIFT_R", 62, HL_MODIFIER_SHIFT);
    const auto       K           = resolvedKey("K", 45);

    const std::array leftHeld = {LEFT_SHIFT, K};
    EXPECT_EQ(result->matches({
                  .heldKeys     = leftHeld,
                  .trigger      = K,
                  .modifiersNow = HL_MODIFIER_SHIFT,
              }),
              BIND_MATCH_FULL);

    const std::array rightHeld = {RIGHT_SHIFT, K};
    EXPECT_EQ(result->matches({
                  .heldKeys     = rightHeld,
                  .trigger      = K,
                  .modifiersNow = HL_MODIFIER_SHIFT,
              }),
              BIND_MATCH_NONE);

    const std::array bothHeld = {LEFT_SHIFT, RIGHT_SHIFT, K};
    EXPECT_EQ(result->matches({
                  .heldKeys     = bothHeld,
                  .trigger      = K,
                  .modifiersNow = HL_MODIFIER_SHIFT,
              }),
              BIND_MATCH_NONE);
}

TEST(Keybinds, SidedModifierCanPartiallyMatch) {
    auto result = makeBind({"SHIFT_L", "K"});
    ASSERT_TRUE(result.has_value());

    const auto       LEFT_SHIFT = resolvedKey("SHIFT_L", 50, HL_MODIFIER_SHIFT);
    const std::array held       = {LEFT_SHIFT};

    EXPECT_EQ(result->matches({
                  .heldKeys     = held,
                  .trigger      = LEFT_SHIFT,
                  .modifiersNow = Input::HL_MODIFIER_NONE,
              }),
              BIND_MATCH_PARTIAL);
}

TEST(Keybinds, UnsidedModifierMatchesEitherSide) {
    auto result = makeBind({"SHIFT", "K"});
    ASSERT_TRUE(result.has_value());

    const auto       RIGHT_SHIFT = resolvedKey("SHIFT_R", 62, HL_MODIFIER_SHIFT);
    const auto       K           = resolvedKey("K", 45);
    const std::array held        = {RIGHT_SHIFT, K};

    EXPECT_EQ(result->matches({
                  .heldKeys     = held,
                  .trigger      = K,
                  .modifiersNow = HL_MODIFIER_SHIFT,
              }),
              BIND_MATCH_FULL);
}

TEST(Keybinds, UnsidedModifierAloneDoesNotPartiallyMatch) {
    auto result = makeBind({"SUPER", "K"});
    ASSERT_TRUE(result.has_value());

    const auto       SUPER = resolvedKey("SUPER_L", 133, HL_MODIFIER_META);
    const std::array held  = {SUPER};
    EXPECT_EQ(result->matches({.heldKeys = held, .trigger = SUPER, .modifiersNow = HL_MODIFIER_META}), BIND_MATCH_NONE);
}

TEST(Keybinds, SingleKeyAllowsOtherHeldKeys) {
    auto result = makeBind({"K"});
    ASSERT_TRUE(result.has_value());

    const auto       A    = resolvedKey("A", 38);
    const auto       K    = resolvedKey("K", 45);
    const std::array held = {A, K};

    EXPECT_EQ(result->matches({
                  .heldKeys = held,
                  .trigger  = K,
              }),
              BIND_MATCH_FULL);
}

TEST(Keybinds, MultiKeyMatchingIsExactAndOrdered) {
    auto result = makeBind({"A", "K"});
    ASSERT_TRUE(result.has_value());

    const auto       A       = resolvedKey("A", 38);
    const auto       K       = resolvedKey("K", 45);
    const auto       X       = resolvedKey("X", 53);
    const std::array partial = {A};

    EXPECT_EQ(result->matches({
                  .heldKeys = partial,
                  .trigger  = A,
              }),
              BIND_MATCH_PARTIAL);

    const std::array full = {A, K};
    EXPECT_EQ(result->matches({
                  .heldKeys = full,
                  .trigger  = K,
              }),
              BIND_MATCH_FULL);
    EXPECT_EQ(result->matches({
                  .heldKeys = full,
                  .trigger  = A,
              }),
              BIND_MATCH_NONE);

    const std::array extra = {A, K, X};
    EXPECT_EQ(result->matches({
                  .heldKeys = extra,
                  .trigger  = K,
              }),
              BIND_MATCH_PARTIAL);
}

TEST(Keybinds, OneHeldKeyCannotSatisfyTwoPatterns) {
    auto result = makeBind({"A", "code:38"});
    ASSERT_TRUE(result.has_value());

    const auto       A    = resolvedKey("A", 38);
    const std::array held = {A};

    EXPECT_EQ(result->matches({
                  .heldKeys = held,
                  .trigger  = A,
              }),
              BIND_MATCH_PARTIAL);
}

TEST(Keybinds, ReleaseBindCompletesOnRelease) {
    auto result = makeBind({"K"}, BIND_FLAG_RELEASE);
    ASSERT_TRUE(result.has_value());

    const auto       K    = resolvedKey("K", 45);
    const std::array held = {K};

    EXPECT_EQ(result->matches({
                  .heldKeys = held,
                  .trigger  = K,
                  .pressed  = true,
              }),
              BIND_MATCH_PARTIAL);
    EXPECT_EQ(result->matches({
                  .heldKeys = held,
                  .trigger  = K,
                  .pressed  = false,
              }),
              BIND_MATCH_FULL);
}

TEST(Keybinds, ReleaseBindUsesModifiersAtPress) {
    auto result = makeBind({"SUPER", "K"}, BIND_FLAG_RELEASE);
    ASSERT_TRUE(result.has_value());

    const auto       K    = resolvedKey("K", 45);
    const std::array held = {K};

    EXPECT_EQ(result->matches({
                  .heldKeys         = held,
                  .trigger          = K,
                  .modifiersNow     = HL_MODIFIER_NONE,
                  .modifiersAtPress = HL_MODIFIER_META,
                  .pressed          = false,
              }),
              BIND_MATCH_FULL);
}

TEST(Keybinds, ReleaseBindIgnoresModifiersPressedAfterTrigger) {
    auto result = makeBind({"SUPER", "K"}, BIND_FLAG_RELEASE);
    ASSERT_TRUE(result.has_value());

    const auto       SUPER = resolvedKey("SUPER_L", 133, HL_MODIFIER_META);
    const auto       K     = resolvedKey("K", 45);
    const std::array held  = {K, SUPER};

    EXPECT_EQ(result->matches({
                  .heldKeys         = held,
                  .trigger          = K,
                  .modifiersNow     = HL_MODIFIER_META,
                  .modifiersAtPress = HL_MODIFIER_NONE,
                  .pressed          = false,
              }),
              BIND_MATCH_NONE);
}

TEST(Keybinds, ModifierReleaseBindIncludesTriggerModifier) {
    auto result = makeBind({"SUPER", "SUPER_L"}, BIND_FLAG_RELEASE);
    ASSERT_TRUE(result.has_value());

    const auto       SUPER = resolvedKey("SUPER_L", 133, HL_MODIFIER_META);
    const std::array held  = {SUPER};

    EXPECT_EQ(result->matches({
                  .heldKeys         = held,
                  .trigger          = SUPER,
                  .modifiersNow     = HL_MODIFIER_NONE,
                  .modifiersAtPress = HL_MODIFIER_NONE,
                  .pressed          = false,
              }),
              BIND_MATCH_FULL);
}

TEST(Keybinds, CatchAllContextHonorsSubmapAndModifiers) {
    auto result = CBind::make({"SUPER", "catchall"}, BIND_FLAG_CATCH_ALL, [] { return SBindResult{}; }, {.metadata = {.submap = "resize"}});
    ASSERT_TRUE(result.has_value());

    EXPECT_TRUE(result->matchesContext({.modifiersNow = HL_MODIFIER_META, .submap = "resize"}));
    EXPECT_FALSE(result->matchesContext({.modifiersNow = HL_MODIFIER_META, .submap = "other"}));
    EXPECT_FALSE(result->matchesContext({.submap = "resize"}));
}
