#!/usr/bin/env bash
# Workspace indicator: numbered workspaces 1-9, preceded by named (special)
# workspaces except the scratchpad.  Keeps a local model of Hyprland's state
# from socket2 events and only calls hyprctl on structural changes.
#
# Entry: {id, name, target, state: focused|active|running|empty,
#         color: blue|red|grey, sep}

declare -A ws_windows=() ws_name=() ws_urgent=() mon_active=()
focused_ws="" focused_monitor=""

hypr_json() { hyprctl -j "$1" 2>/dev/null; }

load_workspaces() {
  local raw id windows name
  raw=$(hypr_json workspaces) || return
  ws_windows=()
  ws_name=()
  while read -r id windows name; do
    ws_windows[$id]=$windows
    ws_name[$id]=$name
  done < <(jaq -r '.[] | "\(.id) \(.windows) \(.name)"' <<<"$raw")
}

load_monitors() {
  local raw name active focused
  raw=$(hypr_json monitors) || return
  mon_active=()
  while read -r name active focused; do
    mon_active[$name]=$active
    if [[ $focused == true ]]; then
      focused_ws=$active
      focused_monitor=$name
    fi
  done < <(jaq -r '.[] | "\(.name) \(.activeWorkspace.id) \(.focused)"' <<<"$raw")
}

load_urgent() {
  local raw id
  raw=$(hypr_json clients) || return
  ws_urgent=()
  while read -r id; do ws_urgent[$id]=1; done \
    < <(jaq -r '.[] | select(.urgent) | .workspace.id' <<<"$raw")
}

# Window address → workspace id.  Events carry the address without the 0x
# prefix that hyprctl uses, which is why urgency never showed up before.
window_workspace() {
  hypr_json clients | jaq -r --arg a "0x$1" 'first(.[] | select(.address == $a) | .workspace.id) // empty'
}

ws_state() { # sets _state / _color for workspace $1
  local id=$1 mon
  _state=empty
  _color=grey
  if [[ $id == "$focused_ws" ]]; then
    _state=focused
  else
    for mon in "${!mon_active[@]}"; do
      [[ ${mon_active[$mon]} == "$id" ]] && { _state=active; break; }
    done
    [[ $_state == empty && ${ws_windows[$id]:-0} -gt 0 ]] && _state=running
  fi
  if [[ $_state != empty ]]; then
    if [[ ${ws_urgent[$id]:-0} == 1 ]]; then _color=red; else _color=blue; fi
  fi
}

entry() { # id name target [sep]
  ws_state "$1"
  printf '{"id":%s,"name":"%s","target":"%s","state":"%s","color":"%s","sep":%s}' \
    "$1" "$(jstr "$2")" "$(jstr "$3")" "$_state" "$_color" "${4:-false}"
}

generate() {
  local out='[' sep='' id i named=()
  for id in "${!ws_name[@]}"; do
    [[ $id == -* && ${ws_name[$id]} != "special:magic" ]] && named+=("$id")
  done
  if ((${#named[@]})); then
    while read -r id; do
      out+="$sep$(entry "$id" "${ws_name[$id]}" "name:${ws_name[$id]}")"
      sep=,
    done < <(printf '%s\n' "${named[@]}" | sort -n)
  fi
  for i in {1..9}; do
    if ((i == 1 && ${#named[@]})); then
      out+="$sep$(entry "$i" "$i" "$i" true)"
    else
      out+="$sep$(entry "$i" "$i" "$i")"
    fi
    sep=,
  done
  emit "$out]"
}

load_workspaces
load_monitors
load_urgent
generate

hypr_socket="$XDG_RUNTIME_DIR/hypr/$HYPRLAND_INSTANCE_SIGNATURE/.socket2.sock"
events='^(workspacev2|focusedmonv2|urgent|openwindow|closewindow|movewindowv2|createworkspacev2|destroyworkspacev2|moveworkspacev2|renameworkspace|monitoraddedv2|monitorremovedv2)>>'

while IFS= read -r line; do
  event=${line%%>>*}
  data=${line#*>>}
  case $event in
    workspacev2) # WORKSPACEID,WORKSPACENAME
      focused_ws=${data%%,*}
      unset "ws_urgent[$focused_ws]"
      [[ -n $focused_monitor ]] && mon_active[$focused_monitor]=$focused_ws
      ;;
    focusedmonv2) # MONNAME,WORKSPACEID
      focused_monitor=${data%%,*}
      focused_ws=${data#*,}
      unset "ws_urgent[$focused_ws]"
      mon_active[$focused_monitor]=$focused_ws
      ;;
    urgent) # WINDOWADDRESS
      id=$(window_workspace "$data")
      [[ -n $id && $id != "$focused_ws" ]] && ws_urgent[$id]=1
      ;;
    openwindow | movewindowv2) load_workspaces ;;
    closewindow)
      load_workspaces
      load_urgent
      ;;
    *)
      load_workspaces
      load_monitors
      ;;
  esac
  generate
done < <(socat -u UNIX-CONNECT:"$hypr_socket" - | rg --line-buffered "$events")
