#!/bin/sh
# Provision the desktop shell that turns Forge's existing X display into a viewable
# desktop: a window manager, a compositor, panels, fonts and native-input tooling.
#
# WHY A SCRIPT AND NOT A SNAPSHOT
# Sprites checkpoints are per-sprite; there is no cross-sprite golden image
# (docs/specs/2026-07-30-sprite-lifecycle-and-cloud-architecture-standard.md:78).
# A checkpoint can only roll one sprite back, so repeatable desktop provisioning has
# to be re-runnable code. This script is idempotent: run it on every boot.
#
# WHAT THIS DOES NOT DO
# It installs no VNC server and opens no listening port. Production Forge sprites are
# provisioned url_settings.auth="public", so anything bound to a port on the sprite
# edge is reachable by anyone who learns the hostname — on a box with passwordless
# sudo. The desktop is streamed over the ALREADY AUTHENTICATED WebSocket via the
# existing Xvfb -> ffmpeg x11grab -> WebRTC pipeline instead. See
# docs/forge-cmmd-auth-lanes.md and .claude/rules/forge-security.md.
#
# It also does not start the window manager. Xvfb is owned by
# WebRtcDisplayCaptureRuntime.ts, so the WM has to be started against that display
# after it exists; this script only puts the pieces on disk. Pass --print-start to
# see the exact commands the runtime should spawn.
set -e

FORGE_DISPLAY="${FORGE_BROWSER_WEBRTC_X_DISPLAY:-:99}"
SCRIPT_DIR="$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd)"
FORGE_REPO_ROOT="${FORGE_REPO_ROOT:-$(dirname -- "$SCRIPT_DIR")}"
FORGE_MARK_SOURCE="${FORGE_REPO_ROOT}/assets/prod/forge-web-favicon-32x32.png"
FORGE_WALLPAPER_SOURCE="${FORGE_REPO_ROOT}/assets/prod/forge-desktop-wallpaper.jpg"
CONFIG_HOME="${XDG_CONFIG_HOME:-${HOME:-/root}/.config}"
TINT2_DIR="${CONFIG_HOME}/tint2"
GTK3_DIR="${CONFIG_HOME}/gtk-3.0"
PIXMAP_DIR=/usr/share/pixmaps
WALLPAPER=/usr/share/backgrounds/forge-display.png

if command -v sudo >/dev/null 2>&1 && [ "$(id -u)" != "0" ]; then
  SUDO=sudo
else
  SUDO=
fi

log() { echo "[forge-display] $*"; }

if [ "${1:-}" = "--print-start" ]; then
  cat <<EOF
DISPLAY=${FORGE_DISPLAY} openbox --sm-disable
DISPLAY=${FORGE_DISPLAY} picom --backend xrender --corner-radius 10
DISPLAY=${FORGE_DISPLAY} tint2 -c ${TINT2_DIR}/forge-top.tint2rc
DISPLAY=${FORGE_DISPLAY} tint2 -c ${TINT2_DIR}/forge-dock.tint2rc
DISPLAY=${FORGE_DISPLAY} feh --bg-fill ${WALLPAPER}
EOF
  exit 0
fi

# ---------------------------------------------------------------------------
# 1. Packages
#
# elementary-xfce-icon-theme is NOT cosmetic and must not be dropped to save time.
# bubblewrap cannot create namespaces inside a sprite ("Unexpected capabilities but
# not setuid"), and GTK3 delegates SVG icon decoding to a bwrap-sandboxed glycin
# loader. Every GTK app therefore aborts on Adwaita's SVG icons with
# "Gtk:ERROR ensure_surface_for_gicon". A raster icon theme sidesteps the SVG path
# entirely. This is also why a full xfce4-session is not used here: xfce4-panel and
# wnck still abort (exit 134) even with the raster theme, whereas a plain WM does not.
# ---------------------------------------------------------------------------
# xvfb leads the list because it is the one package the pipeline cannot run
# without. The header above says Xvfb is "owned by
# WebRtcDisplayCaptureRuntime.ts" — true of STARTING it, but the runtime cannot
# start a binary that was never installed. Measured on a live sprite 2026-08-21:
#   which Xvfb -> not found ; apt-cache policy xvfb -> Installed: (none)
#
# NOTE ON WHERE SPRITES ACTUALLY GET IT: this script ships in the forge-server
# npm package (cli.ts bundles it into dist/, publishGuard requires it there)
# and CMMD's sprite provisioner runs it on boot — the branded tint2 configs it
# writes are what production desktops show. An earlier note here claimed the
# script had no caller; that predates the dist bundling and was stale.
# CMMD PR #4231 additionally restores xvfb in CMMD's own provisioning, so the
# package below may already be present; the install is idempotent either way.
#
# ffmpeg is deliberately NOT added: it IS present from the base image
# (/usr/bin/ffmpeg, verified on the same sprite).
# ---------------------------------------------------------------------------
# DOCK APPS — the one place to add an app to the desktop.
#
# One line each: "<apt package> <desktop id>". This single table drives BOTH the
# apt install and the dock launchers, so the two can never disagree. It used to
# be two hand-maintained lists, and production shipped the failure that invites:
# the dock advertised google-chrome/xfce4-terminal/thunar/mousepad while none of
# them were installed, so it rendered as an empty rounded rectangle.
#
# To add an app: add one line. Nothing else.
#
# Chromium is deliberately absent: Ubuntu 26.04 has no real chromium package (it
# resolves to a snapd-backed transitional one, unusable under tini with no
# systemd), so its launcher is synthesised against Playwright's full Chromium —
# the same build headful capture already requires. See below.
# ---------------------------------------------------------------------------
FORGE_DOCK_APPS="
xfce4-terminal xfce4-terminal
thunar thunar
mousepad org.xfce.mousepad
"

FORGE_DOCK_PACKAGES="$(echo "$FORGE_DOCK_APPS" | awk 'NF {print $1}' | tr '\n' ' ')"

DISPLAY_PACKAGES="xvfb openbox picom tint2 feh x11-utils xdotool imagemagick elementary-xfce-icon-theme fonts-noto-core librsvg2-common"

# ---------------------------------------------------------------------------
# Chromium's runtime shared libraries.
#
# Playwright installs the browser BINARY only; its system libraries are the
# image's problem, and this script is the image's provisioner. Observed live
# 2026-09-01 (sprite 68168604): chrome exited 127 with "error while loading
# shared libraries: libnspr4.so", so every openSession failed — the web app's
# "Start desktop" button appeared to do nothing. Ubuntu 26.04 names (t64
# suffix where the 64-bit time_t transition renamed the package). Idempotent
# and cheap when already present, like everything else in this transaction.
# ---------------------------------------------------------------------------
CHROMIUM_RUNTIME_PACKAGES="libnspr4 libnss3 libasound2t64 libatk-bridge2.0-0t64 libatk1.0-0t64 libatspi2.0-0t64 libcairo2 libcups2t64 libdbus-1-3 libdrm2 libgbm1 libglib2.0-0t64 libpango-1.0-0 libx11-6 libxcb1 libxcomposite1 libxdamage1 libxext6 libxfixes3 libxkbcommon0 libxrandr2"

# ---------------------------------------------------------------------------
# Wait for the dpkg lock before installing.
#
# This script runs inside the sprite's own provisioning, alongside CMMD's apt
# work. Observed live 2026-08-26 on forge-cmmd-cmmd-core-platform:
#   E: Could not get lock /var/lib/dpkg/lock-frontend.
#      It is held by process 24187 (apt-get)
# so nothing installed, no configs, no wallpaper — and the caller had redirected
# output to /dev/null, so the failure was completely silent. Re-running it once
# the lock freed succeeded immediately: contention, not a broken install.
#
# Handled HERE rather than in the caller because this script owns the apt call.
# Every caller benefits and none has to know the lock exists.
#
# BOUNDED. A desktop is cosmetic and must never hold a sprite's provisioning
# open; if the lock is still held after the wait, fall through and let apt fail
# loudly rather than hang.
# ---------------------------------------------------------------------------
wait_for_dpkg_lock() {
  for _ in $(seq 1 60); do
    if command -v fuser >/dev/null 2>&1; then
      fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1 || return 0
    else
      return 0
    fi
    sleep 5
  done
  log "dpkg lock still held after 300s; proceeding and letting apt report it"
}

if command -v apt-get >/dev/null 2>&1; then
  wait_for_dpkg_lock
  log "installing desktop packages"
  DEBIAN_FRONTEND=noninteractive $SUDO apt-get update -y >/dev/null
  # shellcheck disable=SC2086 # word splitting is the intended package list expansion
  DEBIAN_FRONTEND=noninteractive $SUDO apt-get install -y --no-install-recommends $DISPLAY_PACKAGES $CHROMIUM_RUNTIME_PACKAGES $FORGE_DOCK_PACKAGES >/dev/null
else
  log "apt-get unavailable; skipping package install (expects a prebuilt image)"
fi

# ---------------------------------------------------------------------------
# 2. GTK icon theme
#
# Pin the raster theme so GTK never reaches for the sandboxed SVG loader. Written
# per-user rather than system-wide so it travels with $HOME.
# ---------------------------------------------------------------------------
mkdir -p "$GTK3_DIR"
cat > "${GTK3_DIR}/settings.ini" <<'EOF'
[Settings]
gtk-icon-theme-name=elementary-xfce-dark
gtk-application-prefer-dark-theme=1
EOF

# ---------------------------------------------------------------------------
# 3. Brand assets
#
# The mark is the same production favicon the web app ships, copied from the repo
# rather than re-encoded, so the desktop and the product cannot drift apart.
# ---------------------------------------------------------------------------
$SUDO mkdir -p "$PIXMAP_DIR" "$(dirname "$WALLPAPER")"

if [ -f "$FORGE_MARK_SOURCE" ]; then
  $SUDO cp "$FORGE_MARK_SOURCE" "${PIXMAP_DIR}/forge.png"
  if command -v convert >/dev/null 2>&1; then
    $SUDO convert "${PIXMAP_DIR}/forge.png" -resize 22x22 "${PIXMAP_DIR}/forge-bar.png"
  else
    $SUDO cp "${PIXMAP_DIR}/forge.png" "${PIXMAP_DIR}/forge-bar.png"
  fi
else
  log "forge mark not found at ${FORGE_MARK_SOURCE}; top bar will render without it"
fi

# The shipped wallpaper: blackened hammered steel with a Forge-orange ember
# bleeding from the lower centre, generated once and committed rather than
# synthesised per boot. Measured 16.4/255 mean luminance — dark enough that
# white panel text and app icons stay legible on top, which a brighter image
# would quietly ruin.
#
# The radial gradient stays as a FALLBACK, not as the design: this script must
# still produce a usable desktop when run against a tree that predates the
# asset, or one where it was stripped.
if [ -f "$FORGE_WALLPAPER_SOURCE" ]; then
  $SUDO cp "$FORGE_WALLPAPER_SOURCE" "$WALLPAPER"
elif command -v convert >/dev/null 2>&1 && [ ! -f "$WALLPAPER" ]; then
  log "shipped wallpaper missing; falling back to a generated gradient"
  $SUDO convert -size 1920x1200 radial-gradient:'#2A3566'-'#07070B' "$WALLPAPER"
fi

$SUDO tee /usr/share/applications/forge-display-brand.desktop >/dev/null <<EOF
[Desktop Entry]
Version=1.0
Name=Forge
Exec=/bin/true
Terminal=false
Icon=${PIXMAP_DIR}/forge-bar.png
Type=Application
Categories=Development;
EOF

# ---------------------------------------------------------------------------
# 4. Panels
#
# Two tint2 instances: a full-width top bar carrying the Forge mark and a clock,
# and a fixed-width bottom dock. The dock width is explicit on purpose — with
# `panel_size = 0 <h>` tint2 paints the full monitor width, so "center" has no
# visible effect and the dock reads as a full-width bar.
# ---------------------------------------------------------------------------
mkdir -p "$TINT2_DIR"

cat > "${TINT2_DIR}/forge-top.tint2rc" <<EOF
rounded = 0
border_width = 0
background_color = #0B0D14 78
border_color = #000000 0

panel_items = LFC
panel_position = top center horizontal
panel_size = 100% 30
panel_margin = 0 0
panel_padding = 0 0 0
panel_background_id = 1
panel_layer = top
panel_monitor = all
wm_menu = 0
autohide = 0
strut_policy = none

launcher_padding = 12 0 8
launcher_background_id = 0
launcher_icon_size = 20
launcher_tooltip = 1
launcher_item_app = /usr/share/applications/forge-display-brand.desktop

time1_format = %a %b %-d   %-I:%M %p
time1_font = Noto Sans 11
clock_font_color = #FFFFFF 92
clock_padding = 16 0
clock_background_id = 0
EOF

cat > "${TINT2_DIR}/forge-dock.tint2rc" <<'EOF'
rounded = 18
border_width = 1
background_color = #141826 82
border_color = #FFFFFF 14

panel_items = L
panel_position = bottom center horizontal
panel_size = 252 66
panel_margin = 0 14
panel_padding = 12 6 14
panel_background_id = 1
panel_layer = top
panel_monitor = all
wm_menu = 0
autohide = 0
strut_policy = none

launcher_padding = 6 0 12
launcher_background_id = 0
launcher_icon_size = 42
launcher_icon_theme = elementary-xfce-dark
launcher_tooltip = 1
EOF

# ---------------------------------------------------------------------------
# 5. Dock launchers
#
# A browser first, because it is the app a desktop is opened for. Ubuntu 26.04
# has no usable chromium package, so the entry is synthesised against
# Playwright's FULL Chromium — already on the sprite because headful capture
# requires it. Resolved at provision time; if the build is absent the entry is
# skipped and the dock simply has one fewer tile.
# ---------------------------------------------------------------------------
CHROMIUM_BIN="$(ls -d "${HOME:-/root}"/.cache/ms-playwright/chromium-*/chrome-linux64/chrome 2>/dev/null | head -1)"
if [ -n "$CHROMIUM_BIN" ]; then
  # Its OWN artwork, shipped beside the binary. A bare "chromium" icon NAME
  # resolves to nothing here — chromium is not an installed package, so the theme
  # has no entry for it and the dock fell back to a generic gear. Prefer the real
  # logo; fall back to the freedesktop name the icon theme does carry.
  CHROMIUM_LOGO="$(dirname "$CHROMIUM_BIN")/product_logo_48.png"
  if [ -f "$CHROMIUM_LOGO" ]; then
    CHROMIUM_ICON="$CHROMIUM_LOGO"
  else
    CHROMIUM_ICON=web-browser
  fi
  $SUDO tee /usr/share/applications/forge-chromium.desktop >/dev/null <<EOF
[Desktop Entry]
Version=1.0
Name=Chromium
Exec=${CHROMIUM_BIN} %U
Terminal=false
Icon=${CHROMIUM_ICON}
Type=Application
Categories=Network;WebBrowser;
EOF
  echo "launcher_item_app = /usr/share/applications/forge-chromium.desktop" \
    >> "${TINT2_DIR}/forge-dock.tint2rc"
else
  log "playwright chromium not found; dock will render without a browser"
fi

# Then the table. Only advertise launchers whose .desktop file actually exists,
# so a package that failed to install leaves no broken tile behind.
echo "$FORGE_DOCK_APPS" | awk 'NF {print $2}' | while read -r app; do
  entry="/usr/share/applications/${app}.desktop"
  [ -f "$entry" ] && echo "launcher_item_app = ${entry}" >> "${TINT2_DIR}/forge-dock.tint2rc"
done

log "provisioned display shell for ${FORGE_DISPLAY}"
log "start commands: $0 --print-start"
