#!/bin/bash

# alt-tab application switcher using instantmenu
#
# instantmenu grabs the keyboard before it reads stdin, so the menu pipeline
# is spawned first and backend detection plus window enumeration run while
# the menu is already interactive.
# the window tool is picked by session type: wmctrl on X11, instantwmctl on
# instantWM wayland, swaymsg on sway and hyprctl on Hyprland
#
# the currently focused window (one cheap lookup before the menu opens) is
# preselected with --preselect, so the highlight starts on the running window,
# and its row carries a distinct gutter icon

detectwm() {
    if [ -n "$WAYLAND_DISPLAY" ] || [ "$XDG_SESSION_TYPE" = "wayland" ]; then
        if [ -S "${INSTANTWM_SOCKET:-/tmp/instantwm-$UID.sock}" ] && command -v instantwmctl >/dev/null 2>&1; then
            echo instantwmctl
        elif command -v swaymsg >/dev/null 2>&1 && { [ -n "$SWAYSOCK" ] || pgrep -x sway >/dev/null 2>&1; }; then
            echo swaymsg
        elif command -v hyprctl >/dev/null 2>&1 && { [ -n "$HYPRLAND_INSTANCE_SIGNATURE" ] || pgrep -x Hyprland >/dev/null 2>&1; }; then
            echo hyprctl
        fi
    else
        echo wmctrl
    fi
}

listwindows() {
    # {value=...} markup hides the window id from display and search: only
    # the title shows, and selecting an entry outputs the id alone. Every
    # row carries a gutter icon so the list stays aligned; the focused
    # window's row gets a distinct glyph ($focused is looked up before the
    # menu opens)
    case $(detectwm) in
        instantwmctl)
            instantwmctl --json window list |
                jq -r '.[] | "{value=\(.id)\(if .is_focused then " icon=codwindowactive" else " icon=codwindow" end)} \(.title)"'
            ;;
        swaymsg)
            swaymsg -t get_tree |
                jq -r '.. | objects | select(.type == "con" and .name != null) |
                    "{value=\(.id)\(if .focused then " icon=codwindowactive" else " icon=codwindow" end)} \(.name)"'
            ;;
        hyprctl)
            hyprctl -j clients |
                jq -r --arg focused "$focused" '.[] | select(.address != null) |
                    "{value=\(.address)\(if ((.address | ascii_downcase) == ($focused | ascii_downcase)) then " icon=codwindowactive" else " icon=codwindow" end)} \(if .title != null and .title != "" then .title elif .class != null and .class != "" then .class else .address end)"'
            ;;
        wmctrl)
            # wmctrl -l: id desktop host title
            wmctrl -l | awk -v focused="$focused" '{
                id = $1
                sub(/^[^ \t]+[ \t]+[^ \t]+[ \t]+[^ \t]+[ \t]+/, "")
                icon = (tolower(id) == tolower(focused)) ? "codwindowactive" : "codwindow"
                print "{value=" id " icon=" icon "} " $0
            }'
            ;;
    esac
}

# id of the currently focused window, empty when nothing has focus or the
# backend cannot tell; instantmenu's --preselect matches by value, so this
# highlights the running window in the list
focusedwindow() {
    case $(detectwm) in
        instantwmctl)
            instantwmctl --json window list | jq -r '.[] | select(.is_focused) | .id' | head -n 1
            ;;
        swaymsg)
            swaymsg -t get_tree |
                jq -r '.. | objects | select(.type == "con" and .focused == true and .name != null) | .id' |
                head -n 1
            ;;
        hyprctl)
            hyprctl -j activewindow | jq -r '.address // empty' | head -n 1
            ;;
        wmctrl)
            # xprop: _NET_ACTIVE_WINDOW: window id # 0x1a00003
            xprop -root _NET_ACTIVE_WINDOW 2>/dev/null | awk '{print $NF}'
            ;;
    esac
}

# the selection and the focus id are bare window ids; everything else
# (typed text, empty output) must never reach the focus command
validid() {
    case $(detectwm) in
        wmctrl | hyprctl) [[ $1 =~ ^0x[0-9a-fA-F]+$ ]] ;;
        *) [[ $1 =~ ^[0-9]+$ ]] ;;
    esac
}

preselect=()
focused=$(focusedwindow)
if validid "$focused"; then
    preselect=(--preselect "$focused")
fi

selection=$(listwindows | instantmenu \
    --lines 40 \
    --alt-tab \
    --position center \
    --border-width 4 \
    --width 700 \
    --insensitive \
    "${preselect[@]}" \
    --prompt "windows")
[[ -z $selection ]] && exit

# detection is re-run here: cheap (builtins plus one stat), and it doubles as
# the guard against unsupported sessions where the menu opened empty
wm=$(detectwm)
[[ -z $wm ]] && exit

# the selection is the hidden {value=...} payload: the bare window id
id=$selection

# instantmenu also prints typed text that matched nothing, never focus that
validid "$id" || exit

case $wm in
    instantwmctl) exec instantwmctl window focus "$id" ;;
    swaymsg) exec swaymsg "[con_id=$id] focus" ;;
    hyprctl) exec hyprctl dispatch focuswindow address:"$id" ;;
    wmctrl) exec wmctrl -i -a "$id" ;;
esac
