#!/usr/bin/env bash
# Internet radio (radiorecord.ru) and MPRIS "now playing" for the media panel.
#
#   radio                 status loop (deflisten)
#   radio start ID        tune to station ID
#   radio toggle          play / stop the last station
#   radio stop
#   radio vol PERCENT     volume of the radio stream, else of the external player
#   radio mute            mute toggle, same target as vol
#
# mpv runs as the transient user unit eww-radio: independent of the eww
# daemon, and `systemctl --user stop eww-radio` always works.

api="https://www.radiorecord.ru/api"
unit=eww-radio
state_dir=${XDG_STATE_HOME:-$HOME/.local/state}/eww
cache_dir=${XDG_CACHE_HOME:-$HOME/.cache}/eww
id_file=$state_dir/radio-station
mkdir -p "$state_dir" "$cache_dir"

station_ids='[507,522,523,536,537,42532,42602]'
# Extra stations: {id (unique integer >= 1000000), title, stream_hls, icon_fill_white ("" for none)}
custom_stations='[]'

default_song='{"artist":"","song":"","image600":""}'
default_media='{"player":"","status":"Stopped","artist":"","title":"","art":""}'
stations_cache="$cache_dir/radio-stations-$(cksum <<<"$station_ids$custom_stations" | cut -d' ' -f1).json"

# --- stations --------------------------------------------------------------

# Station list, cached for a day (it only changes with station_ids).
stations() {
  if [[ -s $stations_cache && -n $(find "$stations_cache" -mmin -1440 2>/dev/null) ]]; then
    cat "$stations_cache"
    return
  fi
  local rr out
  rr=$(curl -sf --compressed --max-time 8 "$api/stations/" 2>/dev/null \
    | jq -c --argjson ids "$station_ids" \
      '.result.stations | map(select(.id | IN($ids[]))) | map({id, title, stream_hls, icon_fill_white, radiorecord: true})' 2>/dev/null)
  if [[ -z $rr || $rr == null ]]; then
    [[ -s $stations_cache ]] && { cat "$stations_cache"; return; }
    rr='[]'
  fi
  out=$(jq -cn --argjson rr "$rr" --argjson custom "$custom_stations" '$rr + ($custom | map(. + {radiorecord: false}))')
  [[ $rr != '[]' ]] && printf '%s\n' "$out" >"$stations_cache"
  printf '%s\n' "$out"
}

station() { # ID FIELD
  jq -r --argjson id "${1:-0}" --arg f "$2" 'first(.[] | select(.id == $id) | .[$f]) // empty' <<<"$STATIONS"
}

current_id() {
  local id
  id=$(cat "$id_file" 2>/dev/null)
  [[ $id =~ ^[0-9]+$ ]] && echo "$id" || echo 0
}

playing() { systemctl --user is-active --quiet "$unit"; }

# --- playback --------------------------------------------------------------

start() {
  local id=${1:-0} url
  ((id > 0)) || return 1
  STATIONS=$(stations)
  url=$(station "$id" stream_hls)
  [[ -n $url && $url != null ]] || return 1
  systemctl --user stop "$unit" 2>/dev/null
  detach -u "$unit" mpv --no-video --really-quiet --audio-client-name="$unit" "$url" || return 1
  printf '%s\n' "$id" >"$id_file"
}

stop() { systemctl --user stop "$unit" 2>/dev/null; }

# --- volume ----------------------------------------------------------------

# PipeWire node id of the first stream whose line matches $1 (case-insensitive).
stream_node() {
  wpctl status 2>/dev/null | awk -v pat="$1" '
    /Streams:/ { s = 1; next }
    /Sinks:|Sources:|Filters:|Clients:/ { s = 0 }
    s && tolower($0) ~ tolower(pat) && match($0, /[0-9]+\./) { print substr($0, RSTART, RLENGTH - 1); exit }'
}

external_player() { playerctl -l 2>/dev/null | grep -v '^mpv' | head -n1; }

# "pw NODE" while the radio plays, else "mpris PLAYER" for an external player.
volume_target() {
  local n p
  if playing; then
    n=$(stream_node "$unit")
    [[ -n $n ]] && { echo "pw $n"; return; }
  fi
  p=$(external_player)
  [[ -n $p ]] && echo "mpris $p"
}

get_volume() {
  local kind target
  read -r kind target < <(volume_target)
  case $kind in
    pw) wpctl get-volume "$target" 2>/dev/null | awk '{ printf "%d", $2 * 100 + 0.5 }' ;;
    mpris) playerctl -p "$target" volume 2>/dev/null | awk '{ printf "%d", $1 * 100 + 0.5 }' ;;
  esac
}

set_volume() {
  local vol=${1%.*} kind target
  read -r kind target < <(volume_target)
  case $kind in
    pw) wpctl set-volume -l 1.0 "$target" "${vol}%" ;;
    mpris) playerctl -p "$target" volume "$(awk -v v="$vol" 'BEGIN { printf "%.2f", v / 100 }')" 2>/dev/null ;;
  esac
  eww update radio-vol="$vol" 2>/dev/null
}

toggle_mute() {
  local kind target n=""
  read -r kind target < <(volume_target)
  case $kind in
    pw) n=$target ;;
    mpris) n=$(stream_node "${target%%.*}") ;;
  esac
  [[ -n $n ]] || return
  wpctl set-mute "$n" toggle
  if wpctl get-volume "$n" 2>/dev/null | grep -q MUTED; then muted=true; else muted=false; fi
  eww update radio-muted="$muted" 2>/dev/null
}

# --- status loop -----------------------------------------------------------

media_json() { # PLAYER STATUS ARTIST TITLE ART
  jq -cn --arg player "$1" --arg status "${2:-Stopped}" --arg artist "$3" --arg title "$4" --arg art "$5" \
    '{player: $player, status: $status, artist: $artist, title: $title, art: $art}'
}

song_playing() { # ID → SONG from the station history (radiorecord) or the title
  local id=$1 fetched
  if [[ $(station "$id" radiorecord) == true ]]; then
    fetched=$(curl -sf --compressed --max-time 8 "$api/station/history/?id=$id" 2>/dev/null \
      | jq -c '.result.history[0] | {artist: (.artist // ""), song: (.song // ""), image600: (.image600 // "")}' 2>/dev/null)
    [[ -n $fetched && $fetched != null ]] && SONG=$fetched
  else
    SONG=$(jq -cn --arg t "$(station "$id" title)" '{artist: "", song: $t, image600: ""}')
  fi
}

song_idle() { # ID → placeholder with the station's name and logo
  SONG=$(jq -cn --arg t "$(station "$1" title)" --arg i "$(station "$1" icon_fill_white)" \
    '{artist: "", song: $t, image600: $i}')
}

# playerctl --follow line: media\tPLAYER\tSTATUS\tARTIST\tTITLE\tART
media_event() {
  local player status artist title art
  IFS=$'\t' read -r player status artist title art <<<"$1"
  if [[ -z $player ]]; then
    MEDIA=$default_media MEDIA_PLAYER=""
  elif [[ $player != mpv* ]]; then # the radio and jellyfin are mpv, not "external"
    MEDIA=$(media_json "$player" "$status" "$artist" "$title" "$art")
    MEDIA_PLAYER=$player
  fi
}

_media_events() {
  exec playerctl -a --follow metadata \
    --format $'media\t{{playerName}}\t{{status}}\t{{artist}}\t{{title}}\t{{mpris:artUrl}}'
}

refresh() {
  local line id now
  for line in "$@"; do
    [[ $line == media$'\t'* ]] && media_event "${line#media$'\t'}"
  done
  # a player that quit without a final metadata line
  if [[ -n $MEDIA_PLAYER ]] && ! playerctl -l 2>/dev/null | grep -qx "$MEDIA_PLAYER"; then
    MEDIA=$default_media MEDIA_PLAYER=""
  fi

  id=$(current_id)
  now=$SECONDS
  if playing; then
    if [[ $PLAYING != true ]] || ((now - LAST_SONG >= 15)); then
      PLAYING=true
      song_playing "$id"
      LAST_SONG=$now
    fi
  elif [[ $PLAYING != false ]]; then
    PLAYING=false
    song_idle "$id"
  fi

  emit "$(jq -cn --argjson stations "$STATIONS" --argjson radio "$id" \
    --argjson paused "$([[ $PLAYING == true ]] && echo 0 || echo 1)" \
    --argjson song "$SONG" --argjson media "$MEDIA" \
    '{is_paused: $paused, song: $song, radio: $radio, stations: $stations, media: $media}')"

  local v
  v=$(get_volume)
  if [[ -n $v && $v != "$LAST_VOL" ]]; then
    LAST_VOL=$v
    eww update radio-vol="$v" 2>/dev/null
  fi
}

status_loop() {
  STATIONS=$(stations)
  SONG=$default_song MEDIA=$default_media MEDIA_PLAYER="" PLAYING="" LAST_SONG=0 LAST_VOL=""
  refresh
  mux_init
  mux_add _media_events
  mux_tick 2
  mux_loop refresh
}

case ${1:-} in
  start) start "$2" ;;
  stop) stop ;;
  toggle) if playing; then stop; else start "$(current_id)"; fi ;;
  vol) set_volume "$2" ;;
  mute) toggle_mute ;;
  *) status_loop ;;
esac
