#!/usr/bin/env bash

# The Shortcuts page is generated from the compositor, so it cannot drift from
# the real keymap. Two things have to hold for that to be true:
#
#   * every bind Hyprland reports is presented -- the page count matches
#     `hyprctl binds -j` exactly, so adding a bind cannot silently go missing;
#   * every bind carries a description. Hyprland reports Lua-defined binds with
#     dispatcher "__lua" and a bytecode offset as the argument, so a bind
#     without a description has nothing a person could read beside its chord.
#     The service drops those rather than showing a mystery row, which means an
#     undescribed bind disappears from the page -- this contract is what stops
#     that from being a silent loss.

set -euo pipefail

repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
harness="$repo_dir/config/dot/quickshell/keybinds-harness.qml"

fail() {
    printf 'keybinds contract: %s\n' "$1" >&2
    exit 1
}

qs_for_harness() {
    qs -p "$harness" "$@"
}

cleanup() {
    qs_for_harness kill >/dev/null 2>&1 || true
}
trap cleanup EXIT

# ── Every bind in the running compositor must be describable ─────────────────
total="$(hyprctl -j binds | jq 'length')"
undescribed="$(hyprctl -j binds | jq '[.[] | select((.description // "") == "")] | length')"

[[ "$total" -gt 0 ]] || fail 'the compositor reports no binds at all'

if [[ "$undescribed" -ne 0 ]]; then
    printf 'keybinds contract: %s bind(s) have no description and would be dropped from the page:\n' "$undescribed" >&2
    hyprctl -j binds | jq -r '.[] | select((.description // "") == "") | "  modmask=\(.modmask) key=\(.key)"' >&2
    fail 'add a description to each in config/dot/hypr/keybinds.lua'
fi

# ── The page must present all of them ────────────────────────────────────────
qs_for_harness --daemonize >/dev/null
for _ in $(seq 1 40); do
    qs_for_harness ipc show 2>/dev/null | rg -q '^target keybinds-test$' && break
    sleep 0.1
done
qs_for_harness ipc show 2>/dev/null | rg -q '^target keybinds-test$' || fail 'test IPC target did not start'

state='{}'
for _ in $(seq 1 40); do
    state="$(qs_for_harness ipc call keybinds-test status | jq -c .)"
    jq -e '.loaded == true' <<<"$state" >/dev/null 2>&1 && break
    sleep 0.1
done

jq -e '.loaded == true' <<<"$state" >/dev/null || fail "the keymap never loaded: $state"

presented="$(jq -r .count <<<"$state")"
[[ "$presented" == "$total" ]] \
    || fail "the page presents $presented of $total binds — the two must match exactly"

grouped_total="$(jq -r .groupedCount <<<"$state")"
[[ "$grouped_total" == "$total" ]] \
    || fail "grouping lost binds: $grouped_total grouped from $total"

# ── Chords must be rendered for people, not dumped raw ───────────────────────
jq -e '.sample | test("^Super \\+ ")' <<<"$state" >/dev/null \
    || fail "Super+T did not render as a readable chord: $(jq -r .sample <<<"$state")"

[[ "$(jq -r .emptyDescriptions <<<"$state")" == "0" ]] \
    || fail 'a presented bind has an empty description'

trap - EXIT
cleanup
printf 'keybinds contract: PASS (%s binds, all described)\n' "$total"
