#!/usr/bin/env bash

# Going to the window you already have, without giving up opening another.
#
# SUPER opens a new one; SUPER+ALT goes to the one that exists. That order was
# chosen after trying the reverse: making the plain key focus reads well in a
# demo, but it makes "give me another terminal" the awkward case, and on a
# tiling desktop a second terminal beside the first is the normal way to work
# rather than an edge case.
#
# The properties worth pinning:
#
#   1. A window that is already open is focused, not duplicated.
#   2. A window that is not open is launched.
#   3. Patterns are anchored. An unanchored "mail" matches gmail-notifier, and
#      the mail key would raise somebody's notifier.
#   4. Class alone is not always enough. The terminal and the editor are both
#      kitty on this desktop; only the title tells them apart, and a
#      class-only match would make the editor key raise a terminal.
#   5. The focus dispatch is the Lua form. On Hyprland 0.56 `hyprctl dispatch
#      focuswindow address:0x...` is parsed as Lua source and fails, which is
#      a failure the window switcher shipped with because its own contract
#      stubbed hyprctl.
#
# Driven against a stub hyprctl, so no real window is touched.

set -uo pipefail

repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
launcher="$repo_dir/bin/panama-launch"
keybinds="$repo_dir/config/dot/hypr/keybinds.lua"
pick="$repo_dir/config/dot/quickshell/scripts/panama-pick"

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

[[ -x "$launcher" ]] || { printf 'launch-or-focus contract: %s is not executable\n' "$launcher" >&2; exit 1; }

work="$(mktemp -d)"
trap 'rm -rf "$work"' EXIT
calls="$work/calls"
stub="$work/bin"
mkdir -p "$stub"

# Two windows, both kitty: a terminal and an editor. This is the fixture that
# matters, because it is the case a class-only match gets wrong.
cat >"$work/clients.json" <<'JSON'
[
  {"address":"0xaaa","class":"kitty","title":"gib@desktop:~","mapped":true,"focusHistoryID":1},
  {"address":"0xbbb","class":"kitty","title":"nvim ~/src","mapped":true,"focusHistoryID":0},
  {"address":"0xccc","class":"org.gnome.Nautilus","title":"docs","mapped":true,"focusHistoryID":2},
  {"address":"0xddd","class":"gmail-notifier","title":"Mail","mapped":true,"focusHistoryID":3}
]
JSON

cat >"$stub/hyprctl" <<STUB
#!/usr/bin/env bash
if [[ "\$1" == "clients" ]]; then cat "$work/clients.json"; exit 0; fi
printf 'hyprctl %s\n' "\$*" >>"$calls"
STUB
chmod +x "$stub/hyprctl"

run() { : >"$calls"; PATH="$stub:$PATH" "$launcher" "$@" >/dev/null 2>&1; }

# ── 1 & 5. An open window is focused, through the Lua dispatch form ─────────

run --class '^org\.gnome\.Nautilus$' -- some-file-manager
grep -q 'hl.dsp.focus' "$calls" \
    || note 'an already-open window was not focused'
grep -q 'address:0xccc' "$calls" \
    || note 'the wrong window was focused'
grep -q 'dispatch focuswindow' "$calls" \
    && note 'the legacy dispatch form reached hyprctl; Hyprland 0.56 parses it as Lua and fails'

# ── 2. An absent window is launched ─────────────────────────────────────────

run --class '^not-running$' -- true
grep -q 'hl.dsp.focus' "$calls" \
    && note 'a window that is not open was focused anyway'

# ── 3. Anchoring ────────────────────────────────────────────────────────────
#
# The fixture includes gmail-notifier for exactly this. An unanchored pattern
# for the mail client matches it.

run --class '^org\.mozilla\.thunderbird' -- some-mail-client
grep -q 'address:0xddd' "$calls" \
    && note 'the mail pattern matched gmail-notifier'
grep -q 'hl.dsp.focus' "$calls" \
    && note 'a mail client that is not running was treated as running'

# Every shipped pattern is anchored at the start.
while read -r pattern; do
    [[ -n "$pattern" ]] || continue
    [[ "$pattern" == \^* ]] \
        || note "the launch pattern '$pattern' is not anchored, so it can match an unrelated window"
done < <(grep -oE "go_to\(\"[^\"]+\"" "$keybinds" | sed 's/go_to("//; s/"$//')

# ── 4. Class plus title ─────────────────────────────────────────────────────

run --class '^kitty$' --title 'nvim' -- kitty nvim .
grep -q 'address:0xbbb' "$calls" \
    || note 'a title-narrowed match focused the wrong kitty window, so the editor key would raise a terminal'

run --class '^kitty$' -- kitty
grep -q 'address:0xaaa' "$calls" \
    || note 'a class-only match did not focus the most recently used window'

# The editor bind must narrow by title, or it is indistinguishable from the
# terminal bind.
grep -q 'go_to("\^kitty\$", editor, "nvim")' "$keybinds" \
    || note 'the go-to-editor bind does not narrow by title, so it would raise whatever terminal is open'

# The plain application keys must still open a new window. This is the whole
# point of the split, and the easiest thing to lose by accident.
for app in terminal editor browser files calculator mail; do
    grep -qE "bind\(mod \.\. \" \+ [A-Z]\", hl\.dsp\.exec_cmd\($app\)" "$keybinds" \
        || note "the plain $app key no longer opens a new window"
done

# And every go-to bind is on SUPER+ALT, not somewhere that collides with the
# window-manipulation space.
count="$(grep -c 'bind(mod .. " + ALT + ' "$keybinds" || true)"
(( count >= 6 )) || note "only $count go-to binds are on SUPER+ALT; there should be one per application key"

# ── The same dispatch bug must not come back elsewhere ──────────────────────

# Comments stripped first: both files explain the legacy form in prose in order
# to warn against it, and matching that is matching documentation.
grep -v '^[[:space:]]*#' "$pick" | grep -q 'dispatch focuswindow' \
    && note 'panama-pick uses the legacy dispatch form, which fails on Hyprland 0.56'

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

printf 'launch-or-focus contract: PASS\n'
