#!/usr/bin/env bash

# The cheatsheet: every shortcut, on one keypress.
#
# It answers a different question from the Shortcuts settings page. That page
# is "how do I change this" and is worth opening a window for; this is "what
# can I press", asked with your hands already on the keyboard. So the
# properties that matter are that it is one key away, that it closes on the
# same key, and above all that it cannot drift from the real keymap.
#
# That last one is why it reads Keybinds.grouped() rather than a written-down
# list: a shortcut rebound in Settings has to show its new chord here without
# anything being kept in sync. A cheatsheet that lies is worse than none,
# because it is consulted precisely when somebody does not already know.
#
# The live half opens the real surface and checks it maps. The layout is not
# asserted -- how many columns look right is a judgement, not a contract.

set -uo pipefail

repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
module="$repo_dir/config/dot/quickshell/modules/cheatsheet"
shell_qml="$repo_dir/config/dot/quickshell/shell.qml"
state="$repo_dir/config/dot/quickshell/services/ShellState.qml"
keybinds="$repo_dir/config/dot/hypr/keybinds.lua"
rules="$repo_dir/config/dot/hypr/rules.lua"
command_file="$repo_dir/config/local/share/vicinae/scripts/keyboard-shortcuts"

findings=()
note() { findings+=("$1"); }

# ── The module ───────────────────────────────────────────────────────────────

for file in Cheatsheet.qml CheatsheetGroup.qml qmldir; do
    [[ -r "$module/$file" ]] || note "the cheatsheet module has no $file"
done
grep -q '^Cheatsheet 1.0 Cheatsheet.qml$' "$module/qmldir" \
    || note 'Cheatsheet is not registered in its qmldir, so the shell would fail to load entirely'
grep -q '^CheatsheetGroup 1.0 CheatsheetGroup.qml$' "$module/qmldir" \
    || note 'CheatsheetGroup is not registered in its qmldir'

# ── It reads the live keymap ─────────────────────────────────────────────────

grep -q 'Keybinds.grouped()' "$module/Cheatsheet.qml" \
    || note 'the cheatsheet does not read the live keymap, so it can drift from what the keys actually do'
grep -q 'Keybinds.refresh()' "$module/Cheatsheet.qml" \
    || note 'the cheatsheet does not re-read the keymap when opened, so a rebind would not show until the shell restarted'

# ── Wiring ───────────────────────────────────────────────────────────────────

grep -q 'import qs.modules.cheatsheet' "$shell_qml" \
    || note 'shell.qml does not import the cheatsheet module'
grep -qE '^\s*Cheatsheet \{\}' "$shell_qml" \
    || note 'shell.qml never instantiates the cheatsheet'
grep -q 'target: "cheatsheet"' "$shell_qml" \
    || note 'there is no cheatsheet IPC target'
grep -q 'cheatsheetOpen: activeOverlay === "cheatsheet"' "$state" \
    || note 'ShellState does not track the cheatsheet, so it would not close when another overlay opens'
grep -q '"cheatsheet"' <(grep 'Exactly one of these' -A2 "$state") \
    || note 'the ShellState overlay comment does not list the cheatsheet'

# The namespace has to match a layer rule or the surface gets no blur. The
# popover rule matches by prefix, which is why this name was chosen.
grep -q 'qs-popover-cheatsheet' "$module/Cheatsheet.qml" \
    || note 'the cheatsheet does not use a namespace beginning qs-popover, so it would be drawn without blur'
grep -q "namespace = \"^qs-popover\"" "$rules" \
    || note 'the qs-popover layer rule no longer matches by prefix, so the cheatsheet lost its blur'

# ── One key, and the same key closes it ──────────────────────────────────────

grep -q 'qs("cheatsheet", "toggle")' "$keybinds" \
    || note 'no keybind opens the cheatsheet, or it does not toggle so the same key cannot close it'
grep -qE 'bind\(mod \.\. " \+ slash".*description' "$keybinds" \
    || note 'the cheatsheet bind carries no description, which drops it from the keymap it is meant to show'

[[ -x "$command_file" ]] || note 'there is no launcher command for the cheatsheet'

# ── Live ─────────────────────────────────────────────────────────────────────

if ! command -v qs >/dev/null 2>&1 || ! qs ipc call cheatsheet status >/dev/null 2>&1; then
    if (( ${#findings[@]} > 0 )); then
        printf 'cheatsheet contract: %d finding(s)\n' "${#findings[@]}" >&2
        printf '  - %s\n' "${findings[@]}" >&2
        exit 1
    fi
    printf 'cheatsheet contract: PASS (static; no running shell)\n'
    exit 0
fi

was_open="$(qs ipc call cheatsheet status | jq -r .open)"

qs ipc call cheatsheet open >/dev/null
sleep 1
[[ "$(qs ipc call cheatsheet status | jq -r .open)" == "true" ]] \
    || note 'the cheatsheet did not open'
hyprctl layers -j | grep -q 'qs-popover-cheatsheet' \
    || note 'the cheatsheet reports open but its layer never mapped, so nothing is on screen'

# Toggling closes it, which is what makes the opening chord also the closing one.
qs ipc call cheatsheet toggle >/dev/null
sleep 1
[[ "$(qs ipc call cheatsheet status | jq -r .open)" == "false" ]] \
    || note 'toggling an open cheatsheet did not close it'

status="$(qs ipc call cheatsheet status)"
groups="$(jq -r '.groups | length' <<<"$status")"
(( groups >= 4 )) || note "the cheatsheet shows $groups categories; the keymap has six"
(( groups <= 8 )) || note "the cheatsheet shows $groups categories, which suggests grouping fell back to guesswork"

# Every bind is categorised by hypr/keybinds.lua. One that is not falls back to
# substring derivation and lands in a group of its own, which is how the
# modifier-only switcher bind was found sitting in a seventh category by itself.
uncategorised="$(jq -r '.uncategorised | join(", ")' <<<"$status")"
[[ -z "$uncategorised" ]] \
    || note "these binds carry no authored category: $uncategorised"

[[ "$was_open" == "true" ]] && qs ipc call cheatsheet open >/dev/null

if (( ${#findings[@]} > 0 )); then
    printf 'cheatsheet contract: %d finding(s)\n' "${#findings[@]}" >&2
    printf '  - %s\n' "${findings[@]}" >&2
    exit 1
fi

printf 'cheatsheet contract: PASS\n'
