#!/bin/bash

# omarchy:summary=Restart the Omarchy shell
# omarchy:examples=omarchy restart shell

# A caller opened after dev link/unlink may disagree with the still-running
# desktop. The user manager receives Hyprland's environment at session start.
session_omarchy_path=$(systemctl --user show-environment 2>/dev/null | sed -n 's/^OMARCHY_PATH=//p' | tail -n 1)
: "${session_omarchy_path:=$OMARCHY_PATH}"

CONFIG_DIR="$session_omarchy_path/shell"
[[ -f $CONFIG_DIR/shell.qml ]] || { echo "Omarchy shell config not found: $CONFIG_DIR" >&2; exit 1; }

# Allow running from outside the session (e.g. over ssh) by deriving the
# Hyprland instance signature from the newest instance runtime dir.
if [[ -z ${HYPRLAND_INSTANCE_SIGNATURE:-} ]]; then
  hypr_dir=$(find "${XDG_RUNTIME_DIR:-/run/user/$UID}/hypr" -mindepth 1 -maxdepth 1 -type d -printf '%T@ %p\n' 2>/dev/null | sort -n | tail -n 1 | cut -d' ' -f2-)
  [[ -n $hypr_dir ]] && export HYPRLAND_INSTANCE_SIGNATURE=${hypr_dir##*/}
fi

# Restarting a live lock client would kill the lock screen and strand the
# session behind Hyprland's failsafe. But a LOCK session without an active
# locker — the shell died, or its crash handler re-execed a fresh instance
# that holds no lock — sits in that failsafe with no way to authenticate,
# and a restart plus re-lock is the only way back in without a reboot. So
# ask the lock service rather than merely pinging the shell: only a locker
# that reports the lock secure or in progress is worth preserving.
relock=0
if omarchy-hyprland-session-locked; then
  locking=$(OMARCHY_PATH="$session_omarchy_path" OMARCHY_SHELL_IPC_TIMEOUT=0.5s omarchy-shell lock status 2>/dev/null |
    jq -r '.secure or .requested' 2>/dev/null)
  if [[ $locking == "true" ]]; then
    echo "Refusing to restart Omarchy shell while the session is locked." >&2
    exit 1
  fi
  relock=1
fi

# The lock plugin loads asynchronously, so a fresh shell answers ping before
# it can lock, and may even refuse early lock requests while its plugins or
# PAM config are still loading. Mirror omarchy-system-sleep-lock: request the
# lock and poll until the session reports secure, re-requesting as needed, so
# recovery never claims success while the failsafe is still up. The deadline
# is generous because slow plugin discovery delays the lock IPC target.
relock_session() {
  local state deadline=$((SECONDS + 30))

  while (( SECONDS < deadline )); do
    state=$(OMARCHY_PATH="$session_omarchy_path" OMARCHY_SHELL_IPC_TIMEOUT=0.5s omarchy-shell lock status 2>/dev/null |
      jq -r 'if .secure == true then "secure" elif .requested == true then "locking" else "idle" end' 2>/dev/null)

    case $state in
      secure) return 0 ;;
      locking) ;;
      *) OMARCHY_PATH="$session_omarchy_path" OMARCHY_SHELL_IPC_TIMEOUT=0.5s omarchy-shell lock lock >/dev/null 2>&1 ;;
    esac

    sleep 0.1
  done

  return 1
}

# A short timeout keeps an unresponsive user bus from stalling the restart;
# busctl would otherwise wait 25 seconds per probe.
notifications_ready() {
  [[ $(busctl --user --timeout=1s call org.freedesktop.DBus /org/freedesktop/DBus \
    org.freedesktop.DBus NameHasOwner s org.freedesktop.Notifications 2>/dev/null) == "b true" ]]
}

# Core IPC can answer before the notification plugin has registered its bus
# name. Restore an existing notification service before update hooks or setup
# invitations send their one-time toasts; a disabled service need not appear.
notifications_were_running=0
if notifications_ready; then
  notifications_were_running=1
fi

# Each kill stops the oldest matching instance and only returns once it has
# fully exited, so the no-duplicate launch below can't race a dying shell.
while timeout 5 quickshell kill -p "$CONFIG_DIR" --any-display >/dev/null 2>&1; do :; done

# Spawn from Hyprland so the shell inherits the canonical session environment,
# not transient variables from a terminal, SSH connection, or development tool.
hyprctl dispatch 'hl.dsp.exec_cmd("omarchy-launch-shell")' >/dev/null

shell_ready=0
for (( attempt = 0; attempt < 20; attempt++ )); do
  if OMARCHY_PATH="$session_omarchy_path" OMARCHY_SHELL_IPC_TIMEOUT=0.5s omarchy-shell shell ping >/dev/null 2>&1; then
    shell_ready=1
    break
  fi
  sleep 0.1
done
if (( shell_ready == 0 )); then
  echo "Omarchy shell did not become ready after restart." >&2
  exit 1
fi

# The session stays compositor-locked after the old lock client died, so
# re-acquire the lock and let the user authenticate out of it. This comes
# first and depends on nothing else: a user stranded behind the failsafe must
# not wait on the notification plugin, which may be slow or absent.
if (( relock )) && ! relock_session; then
  echo "Omarchy shell restarted, but the session lock was not re-secured." >&2
  exit 1
fi

# Core IPC answers before the notification plugin has registered its bus
# name, so wait for it separately before one-time toasts are sent.
if (( notifications_were_running )); then
  notifications_restored=0
  for (( attempt = 0; attempt < 20; attempt++ )); do
    if notifications_ready; then
      notifications_restored=1
      break
    fi
    sleep 0.1
  done
  if (( notifications_restored == 0 )); then
    echo "Omarchy shell restarted, but its notification service did not become ready." >&2
    exit 1
  fi
fi

# Invitation toasts (like Voxtype/fingerprint setup) die with the old
# shell, and their notify-send waiters hang forever: the dying server
# never emits NotificationClosed. A still-running omarchy-*-invitation
# unit is therefore an unanswered invitation — re-run it so its toast
# reappears on the new shell. Answered invitations have already exited
# and been collected, so the glob no longer matches them.
systemctl --user try-restart 'omarchy-*-invitation.service' 2>/dev/null || true
exit 0
