#!/usr/bin/env bash

# The Alt-Tab overlay, and getting out of it.
#
# The gesture is driven by compositor binds: SUPER+Tab steps, and a bind on
# SUPER RELEASE commits by running `qs ipc call switcher commit`. That works,
# but it has a failure mode nobody had thought about -- if the commit call ever
# fails to land, the overlay stayed up with no keyboard focus, no Escape
# handler and nothing clickable. The only way out was an IPC call typed into a
# terminal the overlay was covering.
#
# So the properties here are about recovery, not about the happy path:
#
#   1. Clicking outside the card puts it away.
#   2. Clicking a row switches to that window. It is the obvious thing to try
#      with a list of windows on screen, and it did nothing.
#   3. An abandoned switch closes itself. A lost commit should cost a pause,
#      not the session.
#   4. Keyboard focus stays with the compositor. Taking it mid-switch is the
#      one thing that would break stepping, so the fixes above must not.

set -uo pipefail

repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
window="$repo_dir/config/dot/quickshell/modules/switcher/WindowSwitcher.qml"
state="$repo_dir/config/dot/quickshell/services/WindowSwitcherState.qml"

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

[[ -r "$window" ]] || { printf 'switcher contract: %s is missing\n' "$window" >&2; exit 1; }

# ── 1 & 2. It responds to a pointer ─────────────────────────────────────────

grep -q 'onClicked: WindowSwitcherState.cancel()' "$window" \
    || note 'clicking outside the switcher does not dismiss it'
grep -q 'onClicked: WindowSwitcherState.selectAt' "$window" \
    || note 'clicking a window in the list does not switch to it'
grep -q 'function selectAt' "$state" \
    || note 'the switcher cannot commit to a specific entry, only to the one stepping landed on'

# selectAt must refuse an index that is not there, or a stale click after the
# list shrinks would activate whatever happens to be at that position.
grep -q 'position >= root.windows.length' "$state" \
    || note 'selectAt does not bound-check its index'

# ── 3. An abandoned switch closes itself ────────────────────────────────────

python3 - "$window" <<'PY' || note 'there is no timeout, so a lost commit leaves the overlay up forever'
import re, sys
text = open(sys.argv[1], encoding="utf-8").read()
match = re.search(r"Timer\s*\{[^}]*running:\s*WindowSwitcherState\.open(.*?)\}", text, re.S)
if not match:
    raise SystemExit(1)
interval = re.search(r"interval:\s*(\d+)", match.group(1))
if not interval:
    raise SystemExit(1)
value = int(interval.group(1))
# Long enough that a deliberate pause mid-gesture is not cut short, short
# enough that a stuck overlay is an annoyance rather than a reason to reboot.
if not (3000 <= value <= 30000):
    print(f"the switcher timeout is {value}ms", file=sys.stderr)
    raise SystemExit(1)
PY

grep -q 'onTriggered: WindowSwitcherState.cancel()' "$window" \
    || note 'the switcher timeout does not cancel the switch'

# ── 4. The gesture itself is untouched ──────────────────────────────────────

grep -q 'WlrKeyboardFocus.None' "$window" \
    || note 'the switcher takes keyboard focus, which breaks stepping mid-switch'

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

# Stepping refuses to open with fewer than two windows (see step() in the
# state service) -- that is designed behavior, not a failure, so a session
# with one window skips the live half rather than failing against it.
open_windows="$(hyprctl -j clients 2>/dev/null | jq length 2>/dev/null || echo 0)"
if command -v qs >/dev/null 2>&1 && (( open_windows >= 2 )) \
    && qs ipc call switcher cancel >/dev/null 2>&1; then
    qs ipc call switcher next >/dev/null 2>&1
    sleep 0.6
    mapped="$(hyprctl layers -j 2>/dev/null | grep -c 'qs-switcher' || true)"
    (( mapped > 0 )) || note 'stepping did not map the switcher'

    qs ipc call switcher commit >/dev/null 2>&1
    sleep 0.6
    mapped="$(hyprctl layers -j 2>/dev/null | grep -c 'qs-switcher' || true)"
    (( mapped == 0 )) || note 'committing did not close the switcher'

    # Leave nothing behind whatever happened above.
    qs ipc call switcher cancel >/dev/null 2>&1
fi

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

printf 'switcher contract: PASS\n'
