#!/usr/bin/env bash
# Per-bot X11 screens inside one Team computer.
# Slot 0 is DISPLAY :1 / VNC 5900 / view 6080 (primary).
# Slot N is DISPLAY :(N+1) / VNC 5900+N / view 6080+N.
set -euo pipefail

export HOME="${HOME:-/home/lazyboy}"
export SHELL=/bin/zsh
export TERM="${TERM:-xterm-256color}"
export LANG="${LANG:-zh_TW.UTF-8}"
export LC_ALL="${LC_ALL:-zh_TW.UTF-8}"
export LANGUAGE="${LANGUAGE:-zh_TW:zh:en}"
export GTK_MODULES="${GTK_MODULES:-atk-bridge}"
export GTK_A11Y="${GTK_A11Y:-atspi}"
export GNOME_ACCESSIBILITY="${GNOME_ACCESSIBILITY:-1}"
export NO_AT_BRIDGE="${NO_AT_BRIDGE:-0}"
export PATH="$HOME/.local/bin:/usr/local/bin:$PATH"

ROOT=/tmp/lazyboy
LIMIT=8
cmd="${1:-}"
shift || true

mkdir -p "$ROOT/screens" /tmp/.X11-unix "$HOME/.browser-profiles"

hydrate_home() {
  mkdir -p "$HOME/.config/xfce4/terminal" "$HOME/.cache" "$HOME/.local/bin"
  if [[ ! -f "$HOME/.zshrc" && -f /usr/share/lazyboy/skel/zshrc ]]; then
    cp /usr/share/lazyboy/skel/zshrc "$HOME/.zshrc"
  fi
  if [[ ! -f "$HOME/.p10k.zsh" && -f /usr/share/lazyboy/skel/p10k.zsh ]]; then
    cp /usr/share/lazyboy/skel/p10k.zsh "$HOME/.p10k.zsh"
  fi
  if [[ ! -f "$HOME/.config/xfce4/terminal/terminalrc" && -f /usr/share/lazyboy/skel/terminalrc ]]; then
    cp /usr/share/lazyboy/skel/terminalrc "$HOME/.config/xfce4/terminal/terminalrc"
  fi
}

hydrate_xfce() {
  local xfce_home="$1"
  mkdir -p \
    "$xfce_home/.config/xfce4/xfconf/xfce-perchannel-xml" \
    "$xfce_home/.config/xfce4/panel" \
    "$xfce_home/.cache" \
    "$xfce_home/.local/share" \
    "$xfce_home/runtime"
  chmod 700 "$xfce_home/runtime" 2>/dev/null || true
  local xml="$xfce_home/.config/xfce4/xfconf/xfce-perchannel-xml"
  local skel_xml=/usr/share/lazyboy/xfce-skel/xfce4/xfconf/xfce-perchannel-xml
  local f
  for f in xfce4-panel.xml xfwm4.xml xfce4-desktop.xml thunar.xml; do
    if [[ -r "$skel_xml/$f" ]]; then
      cp -f "$skel_xml/$f" "$xml/$f" || true
    fi
  done
  if [[ -r /etc/xdg/xfce4/helpers.rc ]]; then
    cp -f /etc/xdg/xfce4/helpers.rc "$xfce_home/.config/xfce4/helpers.rc" || true
  fi
  # Terminals launched by this desktop (boot, panel, menu) inherit
  # XDG_CONFIG_HOME=$xfce_home/.config, not $HOME/.config.
  if [[ -r /usr/share/lazyboy/skel/terminalrc ]]; then
    mkdir -p "$xfce_home/.config/xfce4/terminal"
    cp -f /usr/share/lazyboy/skel/terminalrc "$xfce_home/.config/xfce4/terminal/terminalrc" || true
  fi
}

wait_display() {
  local display="$1"
  local n
  for n in $(seq 1 100); do
    if xdpyinfo -display "$display" >/dev/null 2>&1; then
      return 0
    fi
    sleep 0.1
  done
  return 1
}

port_open() {
  local port="$1"
  python3 - "$port" <<'PY'
import socket, sys
port = int(sys.argv[1])
try:
    s = socket.create_connection(("127.0.0.1", port), 0.2)
    s.close()
except OSError:
    sys.exit(1)
PY
}

wait_port() {
  local port="$1"
  local n
  for n in $(seq 1 50); do
    if port_open "$port"; then
      return 0
    fi
    sleep 0.1
  done
  return 1
}

start_xvfb() {
  local display="$1"
  local number="$2"
  local log="$3"
  if xdpyinfo -display "$display" >/dev/null 2>&1; then
    return 0
  fi
  rm -f "/tmp/.X${number}-lock" "/tmp/.X11-unix/X${number}"
  Xvfb "$display" -screen 0 1280x800x24 -ac +extension RANDR +render -noreset >>"${log}-xvfb.log" 2>&1 9>&- &
  echo $! > "${log}-xvfb.pid"
  wait_display "$display"
}

alive_pidfile() {
  local file="$1"
  [[ -f "$file" ]] || return 1
  local pid
  pid="$(cat "$file" 2>/dev/null || true)"
  [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null
}

start_atspi() {
  local display="$1"
  local log="$2"
  local number="${display#:}"
  if [[ -n "${DBUS_SESSION_BUS_ADDRESS:-}" ]]; then
    printf '%s\n' "$DBUS_SESSION_BUS_ADDRESS" >"$ROOT/screen-${number}.dbus"
  fi
  if [[ -n "${XDG_RUNTIME_DIR:-}" ]]; then
    printf '%s\n' "$XDG_RUNTIME_DIR" >"$ROOT/screen-${number}.runtime"
  fi
  local launcher="" registry=""
  local c
  for c in /usr/libexec/at-spi-bus-launcher /usr/lib/at-spi2-core/at-spi-bus-launcher at-spi-bus-launcher; do
    if [[ -x "$c" ]] || command -v "$c" >/dev/null 2>&1; then
      launcher="$c"
      break
    fi
  done
  for c in /usr/libexec/at-spi2-registryd /usr/lib/at-spi2-core/at-spi2-registryd at-spi2-registryd; do
    if [[ -x "$c" ]] || command -v "$c" >/dev/null 2>&1; then
      registry="$c"
      break
    fi
  done
  if [[ -n "$launcher" ]]; then
    DISPLAY="$display" "$launcher" --launch-immediately >>"${log}-atspi-bus.log" 2>&1 9>&- &
    echo $! >"${log}-atspi-bus.pid"
  fi
  if [[ -n "$registry" ]]; then
    DISPLAY="$display" "$registry" >>"${log}-atspi-registry.log" 2>&1 9>&- &
    echo $! >"${log}-atspi-registry.pid"
  fi
  sleep 0.2
}

start_desktop() {
  local display="$1"
  local xfce_home="$2"
  local log="$3"
  if alive_pidfile "${log}-panel.pid"; then
    return 0
  fi
  hydrate_xfce "$xfce_home"
  export DISPLAY="$display"
  export XDG_CONFIG_HOME="$xfce_home/.config"
  export XDG_CACHE_HOME="$xfce_home/.cache"
  export XDG_DATA_HOME="$xfce_home/.local/share"
  export XDG_RUNTIME_DIR="$xfce_home/runtime"
  export XDG_CURRENT_DESKTOP=XFCE
  if command -v dbus-launch >/dev/null 2>&1; then
    eval "$(dbus-launch --sh-syntax)"
    echo "${DBUS_SESSION_BUS_PID:-}" >"${log}-dbus.pid"
  fi
  start_atspi "$display" "$log"
  if command -v xfconfd >/dev/null 2>&1; then
    xfconfd --daemon >/dev/null 2>&1 || true
  fi
  if command -v xfwm4 >/dev/null 2>&1; then
    xfwm4 --compositor=off --display="$display" --sm-client-disable >>"${log}-wm.log" 2>&1 9>&- &
    echo $! >"${log}-wm.pid"
    sleep 0.3
    xfdesktop --disable-wm-check --sm-client-disable >>"${log}-desktop.log" 2>&1 9>&- &
    echo $! >"${log}-desktop.pid"
    xfce4-panel --disable-wm-check --sm-client-disable >>"${log}-panel.log" 2>&1 9>&- &
    echo $! >"${log}-panel.pid"
  else
    echo "XFCE is missing" >&2
    return 1
  fi
  sleep 0.4
}

start_vnc() {
  local display="$1"
  local vnc_port="$2"
  local view_port="$3"
  local log="$4"
  if ! port_open "$vnc_port"; then
    x11vnc -display "$display" -forever -shared -nopw -listen 127.0.0.1 -rfbport "$vnc_port" \
      -xkb -repeat -cursor arrow -noxdamage -ncache 0 >>"${log}-x11vnc.log" 2>&1 9>&- &
  fi
  if ! port_open "$view_port"; then
    local novnc=/usr/share/novnc
    if [[ ! -d "$novnc" ]]; then
      echo "noVNC is missing" >&2
      return 1
    fi
    websockify --heartbeat=30 --web="$novnc" "0.0.0.0:${view_port}" "127.0.0.1:${vnc_port}" \
      >>"${log}-novnc.log" 2>&1 9>&- &
  fi
  wait_port "$vnc_port"
  wait_port "$view_port"
}

start_cua_driver() {
  local display="$1"
  local log="$2"
  local number="${display#:}"
  if ! command -v cua-driver >/dev/null 2>&1; then
    echo "Cua backend selected but cua-driver is not installed" >&2
    return 1
  fi
  local sock="$ROOT/cua-${number}.sock"
  if [[ "$number" == "1" ]]; then sock="$ROOT/cua.sock"; fi
  if alive_pidfile "${log}-cua.pid"; then
    if [[ -S "$sock" ]]; then return 0; fi
    echo "Cua process exists but its display socket is missing" >&2
    return 1
  fi
  export CUA_DRIVER_RS_HOME="$ROOT/cua-home-${number}"
  export LAZYBOY_CURSOR_COLOR_FILE="$ROOT/screen-${number}.agent-color"
  if [[ -s "$ROOT/screen-${number}.dbus" ]]; then
    export DBUS_SESSION_BUS_ADDRESS="$(cat "$ROOT/screen-${number}.dbus")"
  fi
  if [[ -s "$ROOT/screen-${number}.runtime" ]]; then
    export XDG_RUNTIME_DIR="$(cat "$ROOT/screen-${number}.runtime")"
  fi
  mkdir -p "$CUA_DRIVER_RS_HOME"
  cua-driver telemetry disable >>"${log}-cua.log" 2>&1 || true
  rm -f "$sock"
  DISPLAY="$display" cua-driver serve \
    --grant existing-profile \
    --socket "$sock" \
    --pid-file "${log}-cua.pid" \
    >>"${log}-cua.log" 2>&1 9>&- &
  local bg=$!
  # serve does not persist --pid-file in the pinned driver release.
  printf '%s\n' "$bg" > "${log}-cua.pid"
  local n
  for n in $(seq 1 50); do
    if [[ -S "$sock" ]]; then
      return 0
    fi
    if ! kill -0 "$bg" 2>/dev/null; then
      echo "cua-driver serve exited" >&2
      cat "${log}-cua.log" >&2 || true
      return 1
    fi
    sleep 0.1
  done
  echo "cua-driver socket was not ready" >&2
  cat "${log}-cua.log" >&2 || true
  return 1
}

start_xterm() {
  local display="$1"
  local log="$2"
  if alive_pidfile "${log}-xterm.pid"; then
    return 0
  fi
  DISPLAY="$display" SHELL=/bin/zsh lazyboy-terminal >>"${log}-xterm.log" 2>&1 9>&- &
  echo $! > "${log}-xterm.pid"
}

start_browser() {
  local display="$1"
  local profile="$2"
  local log="$3"
  local number="${display#:}"
  mkdir -p "$profile" "$ROOT"
  if [[ -n "$profile" ]]; then
    printf '%s\n' "$profile" >"$ROOT/screen-${number}.profile"
  fi
  if pgrep -f "chromium.*--user-data-dir=${profile}" >/dev/null 2>&1; then
    return 0
  fi
  rm -f "$profile/SingletonLock" "$profile/SingletonCookie" "$profile/SingletonSocket"
  DISPLAY="$display" HOME="$HOME" LAZYBOY_BROWSER_PROFILE="$profile" \
    lazyboy-browser https://duckduckgo.com >>"${log}-browser.log" 2>&1 9>&- &
  echo $! > "${log}-browser.pid"
}

# Cosmetic update only: saving a color must not boot or focus a desktop.
set_agent_color() {
  local slot="$1" color="$2"
  if ! [[ "$slot" =~ ^[0-9]+$ ]] || (( slot < 0 || slot >= LIMIT )); then
    echo "invalid screen slot" >&2
    return 1
  fi
  if ! [[ "$color" =~ ^#[[:xdigit:]]{6}$ ]]; then
    echo "invalid agent color: expected #RRGGBB" >&2
    return 1
  fi
  local number=$((slot + 1)) color_file
  color_file="$(mktemp "$ROOT/screen-${number}.agent-color.XXXXXX")"
  printf '%s' "$color" > "$color_file"
  mv -f "$color_file" "$ROOT/screen-${number}.agent-color"
}

ensure_slot() {
  local slot="$1"
  local profile="${2:-}"
  local agent_name="${3:-}"
  local agent_color="${4:-}"
  if ! [[ "$slot" =~ ^[0-9]+$ ]] || (( slot < 0 || slot >= LIMIT )); then
    echo "invalid screen slot" >&2
    return 1
  fi
  local number=$((slot + 1))
  if [[ -n "$agent_color" ]]; then
    set_agent_color "$slot" "$agent_color" || return 1
  fi
  # Cua renders the public session label beside its synthetic cursor. Keep
  # the label per display; an atomic replace avoids partial names during calls.
  if [[ -n "$agent_name" ]]; then
    local name_file
    name_file="$(mktemp "$ROOT/screen-${number}.agent-name.XXXXXX")"
    printf '%s' "$agent_name" > "$name_file"
    mv -f "$name_file" "$ROOT/screen-${number}.agent-name"
  fi
  local display=":${number}"
  local vnc_port=$((5900 + slot))
  local view_port=$((6080 + slot))
  local log="$ROOT/screen-${number}"
  local xfce_home="/tmp/xfce-home"
  if (( slot != 0 )); then
    xfce_home="/tmp/xfce-home-${number}"
  fi
  mkdir -p "$ROOT"
  if [[ -n "$profile" ]]; then
    printf '%s\n' "$profile" >"$ROOT/screen-${number}.profile"
  fi
  if xdpyinfo -display "$display" >/dev/null 2>&1 9>&- && port_open "$vnc_port" && port_open "$view_port"; then
    start_cua_driver "$display" "$log" || return 1
    if [[ -n "$profile" ]]; then
      start_browser "$display" "$profile" "$log"
    fi
    printf '{"ok":true,"slot":%s,"display":"%s","viewPort":%s,"vncPort":%s}\n' \
      "$slot" "$display" "$view_port" "$vnc_port"
    return 0
  fi
  (
    flock -w 25 9 || {
      echo "screen ${slot} is busy starting" >&2
      exit 1
    }
    if xdpyinfo -display "$display" >/dev/null 2>&1 9>&- && port_open "$vnc_port" && port_open "$view_port"; then
      start_cua_driver "$display" "$log" || exit 1
      if [[ -n "$profile" ]]; then
        start_browser "$display" "$profile" "$log"
      fi
      printf '{"ok":true,"slot":%s,"display":"%s","viewPort":%s,"vncPort":%s}\n' \
        "$slot" "$display" "$view_port" "$vnc_port"
      exit 0
    fi
    start_xvfb "$display" "$number" "$log" || {
      echo "Xvfb failed on ${display}" >&2
      cat "${log}-xvfb.log" >&2 || true
      exit 1
    }
    start_desktop "$display" "$xfce_home" "$log"
    start_vnc "$display" "$vnc_port" "$view_port" "$log" || exit 1
    start_cua_driver "$display" "$log" || exit 1
    start_xterm "$display" "$log"
    if [[ -n "$profile" ]]; then
      start_browser "$display" "$profile" "$log"
    fi
    if (( slot == 0 )) && [[ -f "${log}-xvfb.pid" ]]; then
      cp "${log}-xvfb.pid" "$ROOT/xvfb-1.pid"
    fi
    printf '{"ok":true,"slot":%s,"display":"%s","viewPort":%s,"vncPort":%s}\n' \
      "$slot" "$display" "$view_port" "$vnc_port"
  ) 9>"$ROOT/screen-${slot}.lock"
}

boot_primary() {
  mkdir -p "$HOME" "$HOME/.local/bin" "$HOME/.config" "$HOME/.browser-profiles" "$ROOT"
  cd "$HOME"
  hydrate_home
  printf '這是 Docker 裡的 Debian 桌面。家目錄會保存在 /home/lazyboy。\n' > "$HOME/README.txt"
  mkdir -p "$HOME/shared" "$HOME/bots"
  ensure_slot 0 ""
}

case "$cmd" in
  color)
    set_agent_color "${1:-}" "${2:-}"
    ;;
  boot-primary)
    boot_primary
    ;;
  ensure)
    ensure_slot "${1:-0}" "${2:-}" "${3:-}" "${4:-}"
    ;;
  *)
    echo "usage: lazyboy-screen color <slot> <#RRGGBB> | boot-primary | ensure <slot> [profile] [agent-name] [agent-color]" >&2
    exit 2
    ;;
esac
