#!/bin/bash

# omarchy:summary=Watch Hyprland monitor events and recover monitor toggles when a monitor is removed

SOCKET="$XDG_RUNTIME_DIR/hypr/$HYPRLAND_INSTANCE_SIGNATURE/.socket2.sock"
LOCK="${XDG_RUNTIME_DIR:-/tmp}/omarchy-monitor-clamshell.lock"
MODELESS_LOCK="${XDG_RUNTIME_DIR:-/tmp}/omarchy-monitor-modeless.lock"

sync_clamshell() {
  (
    flock -n 9 || exit 0
    omarchy-hyprland-monitor-clamshell
  ) 9>"$LOCK"
}

sync_clamshell_after_monitor_change() {
  sync_clamshell

  (
    for delay in 1 3 7; do
      sleep "$delay"
      sync_clamshell
    done
  ) &
}

# Powering the monitor on fires no DRM hotplug, and forcing a re-probe needs
# root, so only a reload re-reads the EDID and only a reload reveals whether it
# worked. Back off: the machine can sit like this all night. The lock keeps one
# loop running across the events that call this; the wait is for an event landing
# in the moment one is exiting, which would otherwise be the last one to come.
recover_modeless() {
  (
    flock -w 1 9 || exit 0

    local delay=3 state reloaded unanswered=0

    while true; do
      omarchy-hyprland-monitor-modeless
      state=$?

      # A monitor reporting a mode is recovered. One the compositor cannot speak
      # for is not an answer, and nothing else will ask again -- but a compositor
      # that stays silent has gone, taking the session and this loop's reason
      # with it.
      (( state == 1 )) && break
      (( state == 2 )) && (( ++unanswered > 20 )) && break
      (( state == 0 )) && unanswered=0

      reloaded=0
      # Reloading into half-replaced package config is what the guard prevents.
      if (( state == 0 )) && ! omarchy-hyprland-reload-guard paused; then
        hyprctl reload >/dev/null 2>&1 || true
        reloaded=1
      fi

      sleep "$delay"
      (( reloaded )) && (( delay = delay * 2 > 60 ? 60 : delay * 2 ))
    done
  ) 9>"$MODELESS_LOCK" &
}

poll_clamshell_state() {
  while true; do
    sleep 2
    sync_clamshell
  done
}

# The internal panel is only ever disabled while a laptop is docked (lid shut
# with an external monitor active), so the reconciliation poll only has anything
# to reconcile in that window. Run it while docked, stop it when undocked, and
# never on a machine without a lid. Lid open/close itself is handled by the
# Hyprland "switch:*:Lid Switch" binds; this poll is the recovery backstop for
# drift those binds can miss (e.g. across suspend/resume).
poll_pid=""
sync_poll_state() {
  if omarchy-hw-laptop && omarchy-hyprland-monitor-external-active; then
    if [[ -z $poll_pid ]] || ! kill -0 "$poll_pid" 2>/dev/null; then
      poll_clamshell_state &
      poll_pid=$!
    fi
  elif [[ -n $poll_pid ]]; then
    kill "$poll_pid" 2>/dev/null
    poll_pid=""
  fi
}

sync_clamshell_after_monitor_change
sync_poll_state
recover_modeless

# Process substitution (not a pipe) keeps this loop in the main shell, so
# sync_poll_state can start and stop the background poll as monitors come and go.
while read -r event; do
  case "$event" in
    monitoradded\>\>*|monitoraddedv2\>\>*)
      sync_clamshell_after_monitor_change
      sync_poll_state
      recover_modeless
      ;;
    monitorremoved\>\>*|monitorremovedv2\>\>*)
      sync_clamshell_after_monitor_change
      sync_poll_state
      recover_modeless
      ;;
    # A reload while the monitor is unpowered leaves it at 0x0 with no hotplug
    # to notice, same as at boot. Our own recovery reload lands here too, and is
    # a no-op while its loop still holds the pid.
    configreloaded\>\>*)
      recover_modeless
      ;;
  esac
done < <(socat -U - "UNIX-CONNECT:$SOCKET")
