#!/usr/bin/env bash
# Network state for the bar icons and the net panel: wifi, ethernet and USB
# tethering.  Refreshes on link/address changes and every 30 s (signal level).

wifi_iface() { awk 'NR > 2 { sub(":", "", $1); print $1; exit }' /proc/net/wireless; }
eth_iface() { ip -o link 2>/dev/null | awk '$2 ~ /^en[op]/ { sub(":", "", $2); print $2; exit }'; }
usb_iface() { ip -o link 2>/dev/null | awk '$2 ~ /^(usb[0-9]|enx)/ { sub(":", "", $2); print $2; exit }'; }
ipv4() { ip -4 -o addr show "$1" 2>/dev/null | awk '{ print $4; exit }'; }

signal_icon() {
  local dbm=${1:-}
  [[ -z $dbm ]] && { echo "󰤮"; return; }
  if ((dbm >= -50)); then echo "󰤨"
  elif ((dbm >= -60)); then echo "󰤥"
  elif ((dbm >= -70)); then echo "󰤢"
  elif ((dbm >= -80)); then echo "󰤟"
  else echo "󰤯"; fi
}

band() {
  local f=${1:-0}
  if ((f >= 5925)); then echo "6 GHz"
  elif ((f >= 4900)); then echo "5 GHz"
  elif ((f >= 2400)); then echo "2.4 GHz"
  else echo ""; fi
}

generation() {
  case ${1:-} in
    7) echo "Wi-Fi 7" ;; 6) echo "Wi-Fi 6" ;; 5) echo "Wi-Fi 5" ;; 4) echo "Wi-Fi 4" ;; *) echo "" ;;
  esac
}

state() {
  local wifi eth usb
  wifi=$(wifi_iface)
  eth=$(eth_iface)
  usb=$(usb_iface)

  local w_enabled=false w_connected=false w_icon="󰤮" w_ssid="" w_ip="" w_freq=0 w_gen="" w_signal=0 wpa
  rfkill list wlan 2>/dev/null | grep -q "Soft blocked: yes" || w_enabled=true
  if [[ -n $wifi ]]; then
    w_ip=$(ipv4 "$wifi")
    if [[ -n $w_ip ]]; then
      w_connected=true
      wpa=$(wpa_cli -g "/run/wpa_supplicant/$wifi" status 2>/dev/null)
      w_ssid=$(awk -F= '/^ssid=/ { print $2; exit }' <<<"$wpa")
      w_freq=$(awk -F= '/^freq=/ { print $2; exit }' <<<"$wpa")
      w_gen=$(generation "$(awk -F= '/^wifi_generation=/ { print $2; exit }' <<<"$wpa")")
      w_signal=$(awk -v i="$wifi:" '$1 == i { sub(/\./, "", $4); print $4; exit }' /proc/net/wireless)
      w_icon=$(signal_icon "$w_signal")
    fi
  fi

  local e_connected=false e_ip="" e_speed="" spd
  if [[ -n $eth ]] && ip -o link show "$eth" 2>/dev/null | grep -q "state UP"; then
    e_connected=true
    e_ip=$(ipv4 "$eth")
    spd=$(cat "/sys/class/net/$eth/speed" 2>/dev/null)
    [[ ${spd:-0} -gt 0 ]] 2>/dev/null && e_speed="${spd} Mbps"
  fi

  local u_connected=false u_ip=""
  if [[ -n $usb ]]; then
    u_ip=$(ipv4 "$usb")
    [[ -n $u_ip ]] && u_connected=true
  fi

  printf '{"wifi":{"connected":%s,"enabled":%s,"icon":"%s","ssid":"%s","ip":"%s","freq":%s,"band":"%s","gen":"%s","signal":%s},' \
    "$w_connected" "$w_enabled" "$w_icon" "$(jstr "$w_ssid")" "$w_ip" "${w_freq:-0}" "$(band "$w_freq")" "$w_gen" "${w_signal:-0}"
  printf '"ethernet":{"connected":%s,"interface":"%s","ip":"%s","speed":"%s"},' \
    "$e_connected" "$eth" "$e_ip" "$e_speed"
  printf '"usb":{"connected":%s,"interface":"%s","ip":"%s"},' \
    "$u_connected" "$usb" "$u_ip"
  wg_json
  printf '}'
}

# --- WireGuard: interfaces from the wireguard-<name>.service units (so a
# tunnel that is down still shows up) plus any live wireguard-type link.
# `wg show` needs root, so peer names and the endpoint come from the peer
# units instead.

wg_ifaces() {
  {
    systemctl list-units --all --plain --no-legend 'wireguard-*.service' 2>/dev/null \
      | awk '$1 !~ /-peer-/ { sub(/^wireguard-/, "", $1); sub(/\.service$/, "", $1); print $1 }'
    ip -d -o link 2>/dev/null | awk '/ wireguard / { sub(":", "", $2); print $2 }'
  } | sort -u
}

human_bytes() {
  awk -v b="${1:-0}" 'BEGIN {
    split("B KiB MiB GiB TiB", u, " "); i = 1
    while (b >= 1024 && i < 5) { b /= 1024; i++ }
    printf (i == 1 ? "%d %s" : "%.1f %s"), b, u[i] }'
}

wg_json() { # prints the "wireguard" array and the "vpn" summary
  local name up ip rx tx peers endpoint peer_unit peer_script sep="" out="[" vpn_up=false vpn_name=""
  while read -r name; do
    [[ -n $name ]] || continue
    up=false ip="" rx=0 tx=0 endpoint=""
    if [[ -d /sys/class/net/$name ]]; then
      ip -o link show "$name" 2>/dev/null | grep -q '<[^>]*UP' && up=true
      ip=$(ipv4 "$name")
      rx=$(<"/sys/class/net/$name/statistics/rx_bytes")
      tx=$(<"/sys/class/net/$name/statistics/tx_bytes")
    fi
    peers=$(systemctl list-units --all --plain --no-legend "wireguard-$name-peer-*.service" 2>/dev/null \
      | awk '{ print $1 }' | sed "s/^wireguard-$name-peer-//; s/\.service\$//" | paste -sd, -)
    peer_unit=$(systemctl list-units --all --plain --no-legend "wireguard-$name-peer-*.service" 2>/dev/null \
      | awk 'NR == 1 { print $1 }')
    # NixOS puts `wg set … endpoint "host:port"` in the peer unit's start script
    if [[ -n $peer_unit ]]; then
      peer_script=$(systemctl show -p ExecStart --value "$peer_unit" 2>/dev/null \
        | sed -n 's/.*path=\([^ ;]*\).*/\1/p' | awk 'NR == 1')
      [[ -r $peer_script ]] && endpoint=$(grep -o 'endpoint "[^"]*"' "$peer_script" 2>/dev/null \
        | awk -F'"' 'NR == 1 { print $2 }')
    fi
    if [[ $up == true && $vpn_up == false ]]; then vpn_up=true vpn_name=$name; fi
    [[ -z $vpn_name ]] && vpn_name=$name
    out+="$sep{\"name\":\"$(jstr "$name")\",\"up\":$up,\"ip\":\"$ip\",\"rx\":\"$(human_bytes "$rx")\",\"tx\":\"$(human_bytes "$tx")\",\"peers\":\"$(jstr "$peers")\",\"endpoint\":\"$(jstr "$endpoint")\"}"
    sep=,
  done < <(wg_ifaces)
  printf '"wireguard":%s],"vpn":{"up":%s,"name":"%s"}' "$out" "$vpn_up" "$(jstr "$vpn_name")"
}

refresh() { emit "$(state)"; }
_link_events() { exec ip monitor link address; }

refresh
mux_init
mux_add _link_events
mux_tick 30
mux_loop refresh
