#!/usr/bin/env bash
# headview — turn a headless Adom container into a browser-accessible desktop.
#
# Spins up Xvfb + fluxbox + x11vnc + noVNC inside the container, maps a
# *.adom.cloud port so Cloudflare serves it, and prints a URL you can open
# from any browser (or as a Hydrogen webview tab). The container hosts the
# site itself — no SSH tunnel, no local viewer.
#
# Design: AI-oriented output — every line starts with OK: or ERROR:, errors
# carry a Hint:. Colors only on a TTY (stripped when piped/logged).

set -uo pipefail

HEADVIEW_VERSION="0.1.0"

# ---------------------------------------------------------------- output helpers
if [ -t 1 ]; then C_OK=$'\033[32m\033[1m'; C_CYAN=$'\033[36m'; C_YEL=$'\033[33m'; C_DIM=$'\033[2m'; C_RST=$'\033[0m'
else C_OK=""; C_CYAN=""; C_YEL=""; C_DIM=""; C_RST=""; fi
if [ -t 2 ]; then E_ERR=$'\033[31m\033[1m'; E_DIM=$'\033[2m'; E_RST=$'\033[0m'; else E_ERR=""; E_DIM=""; E_RST=""; fi

ok()   { printf '%sOK:%s %s\n'   "$C_OK"  "$C_RST" "$*"; }
note() { printf '%s%s%s\n'       "$C_DIM" "$*" "$C_RST"; }
err()  { printf '%sERROR:%s %s\n' "$E_ERR" "$E_RST" "$*" >&2; }
hint() { printf '%sHint:%s %s\n' "$E_DIM" "$E_RST" "$*" >&2; }
die()  { err "$1"; [ -n "${2:-}" ] && hint "$2"; exit 1; }

# ---------------------------------------------------------------- state
STATE_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/headview"
STATE_FILE="$STATE_DIR/state.env"
NOVNC_HOME="$HOME/.local/share/novnc"     # git-clone fallback web root
mkdir -p "$STATE_DIR"

save_state() { # key value
  mkdir -p "$STATE_DIR"; touch "$STATE_FILE"
  grep -v "^$1=" "$STATE_FILE" 2>/dev/null > "$STATE_FILE.tmp" || true
  echo "$1=$2" >> "$STATE_FILE.tmp"; mv "$STATE_FILE.tmp" "$STATE_FILE"
}
load_state() { [ -f "$STATE_FILE" ] && set -a && . "$STATE_FILE" && set +a || true; }

# ---------------------------------------------------------------- defaults
DISPLAY_NUM=":99"
GEOMETRY="1600x1000"
DEPTH="24"
VNC_PORT="5900"
WEB_PORT="6080"
PREFIX="desktop"
APP=""
USE_PASSWORD="1"

# ---------------------------------------------------------------- deps
have() { command -v "$1" >/dev/null 2>&1; }

novnc_webroot() {
  if [ -f /usr/share/novnc/vnc.html ]; then echo /usr/share/novnc
  elif [ -f "$NOVNC_HOME/vnc.html" ]; then echo "$NOVNC_HOME"
  else echo ""; fi
}

ensure_deps() {
  local need=()
  have Xvfb    || need+=(xvfb)
  have x11vnc  || need+=(x11vnc)
  have fluxbox || need+=(fluxbox)
  have xdpyinfo|| need+=(x11-utils)
  # noVNC + websockify: prefer apt packages
  if [ -z "$(novnc_webroot)" ] || ! have websockify; then need+=(novnc websockify); fi
  if [ ${#need[@]} -gt 0 ]; then
    note "installing: ${need[*]}"
    if have sudo; then sudo apt-get update -qq && sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq "${need[@]}" >/dev/null 2>&1
    else apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get install -y -qq "${need[@]}" >/dev/null 2>&1; fi
  fi
  # git-clone fallback if apt didn't provide a web root
  if [ -z "$(novnc_webroot)" ]; then
    note "apt novnc unavailable — cloning noVNC from GitHub"
    have git || die "git not available and noVNC not installed" "sudo apt-get install -y git novnc"
    [ -d "$NOVNC_HOME" ] || git clone --depth 1 -q https://github.com/novnc/noVNC "$NOVNC_HOME" || \
      die "failed to fetch noVNC" "check the container has outbound network to github.com"
    [ -d "$NOVNC_HOME/utils/websockify" ] || git clone --depth 1 -q https://github.com/novnc/websockify "$NOVNC_HOME/utils/websockify" || true
  fi
  for b in Xvfb x11vnc fluxbox; do have "$b" || die "$b still missing after install" "install it manually: sudo apt-get install -y $b"; done
}

# ---------------------------------------------------------------- self slug
container_slug() {
  local j; j="$(adom-cli carbon containers current 2>/dev/null)" || return 1
  printf '%s' "$j" | python3 -c 'import sys,json;print(json.load(sys.stdin).get("slug",""))' 2>/dev/null
}

# find an existing mapped hostname whose internal port == WEB_PORT
existing_hostname_for_port() {
  local port="$1" j
  j="$(adom-cli carbon containers current 2>/dev/null)" || return 1
  printf '%s' "$j" | python3 -c '
import sys,json
p=int(sys.argv[1]); d=json.load(sys.stdin)
for host,port in (d.get("port_mappings") or {}).items():
    if int(port)==p: print(host); break
' "$port" 2>/dev/null
}

start_websockify() {
  local webroot="$1"
  pkill -f "websockify.*:$WEB_PORT" 2>/dev/null; pkill -f "novnc_proxy.*$WEB_PORT" 2>/dev/null; sleep 1
  if have websockify; then
    nohup websockify --web="$webroot" "0.0.0.0:$WEB_PORT" "localhost:$VNC_PORT" >/tmp/headview-web.log 2>&1 &
  else
    nohup "$NOVNC_HOME/utils/novnc_proxy" --vnc "localhost:$VNC_PORT" --listen "$WEB_PORT" >/tmp/headview-web.log 2>&1 &
  fi
}

# ---------------------------------------------------------------- commands
cmd_up() {
  ensure_deps
  local webroot; webroot="$(novnc_webroot)"
  [ -n "$webroot" ] || die "noVNC web root not found" "run: sudo apt-get install -y novnc"

  export DISPLAY="$DISPLAY_NUM"

  # 1) Xvfb
  if ! pgrep -f "Xvfb $DISPLAY_NUM" >/dev/null; then
    nohup Xvfb "$DISPLAY_NUM" -screen 0 "${GEOMETRY}x${DEPTH}" >/tmp/headview-xvfb.log 2>&1 &
    sleep 2
  fi
  pgrep -f "Xvfb $DISPLAY_NUM" >/dev/null || die "Xvfb failed to start" "check /tmp/headview-xvfb.log"

  # 2) window manager
  pgrep -f "fluxbox" >/dev/null || { nohup fluxbox >/tmp/headview-fluxbox.log 2>&1 &  sleep 1; }

  # 3) password
  local pw=""
  if [ "$USE_PASSWORD" = "1" ]; then
    pw="${HEADVIEW_PASSWORD:-}"
    if [ -z "$pw" ]; then pw="hv$(od -An -N4 -tx1 /dev/urandom | tr -d ' \n')"; fi
    mkdir -p "$HOME/.vnc"
    x11vnc -storepasswd "$pw" "$HOME/.vnc/passwd" >/dev/null 2>&1
    save_state PASSWORD "$pw"
  else
    save_state PASSWORD ""
  fi

  # 4) x11vnc (loopback only — noVNC is the public face)
  pkill -f "x11vnc.*:$VNC_PORT" 2>/dev/null; pkill -f "x11vnc -display $DISPLAY_NUM" 2>/dev/null; sleep 1
  if [ "$USE_PASSWORD" = "1" ]; then
    nohup x11vnc -display "$DISPLAY_NUM" -localhost -rfbauth "$HOME/.vnc/passwd" -forever -shared -rfbport "$VNC_PORT" -bg >/tmp/headview-x11vnc.log 2>&1
  else
    nohup x11vnc -display "$DISPLAY_NUM" -localhost -nopw -forever -shared -rfbport "$VNC_PORT" -bg >/tmp/headview-x11vnc.log 2>&1
  fi
  sleep 2
  grep -q "Listening for VNC" /tmp/headview-x11vnc.log || die "x11vnc failed to listen on $VNC_PORT" "check /tmp/headview-x11vnc.log"

  # 5) noVNC web bridge
  start_websockify "$webroot"; sleep 3
  curl -s -o /dev/null --max-time 6 "http://127.0.0.1:$WEB_PORT/vnc.html" || die "noVNC not serving on $WEB_PORT" "check /tmp/headview-web.log"

  # 6) port mapping
  local host; host="$(existing_hostname_for_port "$WEB_PORT")"
  if [ -z "$host" ]; then
    local slug; slug="$(container_slug)"
    [ -n "$slug" ] || die "could not determine this container's slug" "run inside an Adom container; check: adom-cli carbon containers current"
    note "adding port mapping ${WEB_PORT} -> ${PREFIX}-*.adom.cloud"
    local out; out="$(adom-cli carbon containers port-add --port "$WEB_PORT" --prefix "$PREFIX" "$slug" 2>&1)" || die "port-add failed: $out" "check adom-cli auth"
    host="$(printf '%s' "$out" | python3 -c 'import sys,json;print(json.load(sys.stdin).get("hostname",""))' 2>/dev/null)"
  fi
  [ -n "$host" ] || die "no mapped hostname" "run: adom-cli carbon containers current  # to inspect mappings"

  # 7) optional app
  if [ -n "$APP" ]; then DISPLAY="$DISPLAY_NUM" nohup $APP >/tmp/headview-app.log 2>&1 & note "launched app: $APP"; fi

  save_state DISPLAY_NUM "$DISPLAY_NUM"
  save_state GEOMETRY "$GEOMETRY"
  save_state VNC_PORT "$VNC_PORT"
  save_state WEB_PORT "$WEB_PORT"
  save_state HV_HOST "$host"

  local url="https://$host/vnc.html?autoconnect=1&resize=scale"
  ok "Container desktop is live and hosted by the container itself."
  note "  URL:      $url"
  [ "$USE_PASSWORD" = "1" ] && note "  Password: $pw"
  note "  Display:  $DISPLAY_NUM (${GEOMETRY})   VNC:$VNC_PORT  web:$WEB_PORT"
  note "Open it in any browser, or as a Hydrogen tab:  headview open"
  note "Launch apps onto it with:  DISPLAY=$DISPLAY_NUM your-app &"
}

cmd_down() {
  load_state
  local removed_map=""
  pkill -f "websockify.*:${WEB_PORT:-6080}" 2>/dev/null
  pkill -f "novnc_proxy" 2>/dev/null
  pkill -f "x11vnc.*:${VNC_PORT:-5900}" 2>/dev/null
  pkill -f "x11vnc -display ${DISPLAY_NUM:-:99}" 2>/dev/null
  pkill -f "fluxbox" 2>/dev/null
  pkill -f "Xvfb ${DISPLAY_NUM:-:99}" 2>/dev/null
  if [ "${1:-}" = "--keep-mapping" ]; then :; else
    if [ -n "${HV_HOST:-}" ]; then
      local slug sub; slug="$(container_slug)"; sub="${HV_HOST%.adom.cloud}"
      if [ -n "$slug" ] && [ -n "$sub" ]; then
        adom-cli carbon containers port-remove "$slug" "$sub" >/dev/null 2>&1 && removed_map=" and removed port mapping ($sub)" || true
      fi
    fi
  fi
  ok "Tore down headview desktop$removed_map."
}

cmd_status() {
  load_state
  local up=0
  printf 'Xvfb:       ' ; pgrep -f "Xvfb ${DISPLAY_NUM:-:99}" >/dev/null && { echo running; up=1; } || echo stopped
  printf 'x11vnc:     ' ; pgrep -f "x11vnc" >/dev/null && echo running || echo stopped
  printf 'websockify: ' ; pgrep -f "websockify|novnc_proxy" >/dev/null && echo running || echo stopped
  if [ -n "${HV_HOST:-}" ]; then note "URL:      https://$HV_HOST/vnc.html?autoconnect=1&resize=scale"; [ -n "${PASSWORD:-}" ] && note "Password: $PASSWORD"; fi
  [ "$up" = 1 ] && ok "headview is up." || ok "headview is not running."
}

cmd_url() {
  load_state
  [ -n "${HV_HOST:-}" ] || die "no desktop is running" "start one with: headview up"
  echo "https://$HV_HOST/vnc.html?autoconnect=1&resize=scale"
}

cmd_password() {
  load_state
  [ -n "${PASSWORD:-}" ] && ok "VNC password: $PASSWORD" || ok "no password set (passwordless mode)"
}

cmd_open() {
  load_state
  [ -n "${HV_HOST:-}" ] || die "no desktop is running" "start one with: headview up"
  [ -n "${VSCODE_PROXY_URI:-}" ] || die "Hydrogen editor not detected (VSCODE_PROXY_URI unset)" "run this from a Hydrogen workspace terminal, or open the URL in a browser: headview url"
  local url="https://$HV_HOST/vnc.html?autoconnect=1&resize=scale"
  adom-cli hydrogen webview open-or-refresh --name "Container Desktop" --url "$url" && ok "Opened 'Container Desktop' webview tab." || die "failed to open webview" "check adom-cli hydrogen webview"
}

cmd_install() {
  local skill_dir="$HOME/.claude/skills/headview"
  mkdir -p "$skill_dir"
  local url="https://wiki.adom.inc/static/apps/headview/SKILL.md"
  if curl -fsSL --max-time 5 "$url" -o "$skill_dir/SKILL.md" 2>/dev/null; then
    ok "Installed headview skill (fetched from wiki)."
  elif [ -f "$(dirname "$0")/SKILL.md" ]; then
    cp "$(dirname "$0")/SKILL.md" "$skill_dir/SKILL.md"; ok "Installed headview skill (bundled copy; wiki unreachable)."
  else
    die "could not install skill" "SKILL.md not found next to the binary and wiki unreachable"
  fi
}

usage() {
  cat <<EOF
headview v$HEADVIEW_VERSION — a browser desktop for a headless Adom container

USAGE: headview <command> [options]

COMMANDS:
  up            Start the desktop (Xvfb+fluxbox+x11vnc+noVNC), map a port, print the URL
  down          Stop everything and remove the port mapping (--keep-mapping to keep it)
  status        Show what's running and the URL/password
  url           Print the desktop URL
  password      Print the VNC password
  open          Open the desktop as a Hydrogen webview tab (needs a Hydrogen terminal)
  install       Install the headview skill into ~/.claude/skills/
  version       Print version

'up' OPTIONS:
  --display :N        X display (default :99)
  --geometry WxH      screen size (default 1600x1000)
  --vnc-port N        internal VNC port (default 5900)
  --web-port N        internal noVNC/web port to map (default 6080)
  --prefix NAME       subdomain prefix for the mapping (default desktop)
  --app "CMD"         launch CMD on the desktop after starting
  --no-password       run passwordless (only do this behind other access control)

EXAMPLES:
  headview up
  headview up --geometry 1920x1080 --app "kicad"
  headview url
  headview down
EOF
}

# ---------------------------------------------------------------- arg parse
cmd="${1:-}"; shift || true
while [ $# -gt 0 ]; do
  case "$1" in
    --display)  DISPLAY_NUM="$2"; shift 2;;
    --geometry) GEOMETRY="$2"; shift 2;;
    --vnc-port) VNC_PORT="$2"; shift 2;;
    --web-port) WEB_PORT="$2"; shift 2;;
    --prefix)   PREFIX="$2"; shift 2;;
    --app)      APP="$2"; shift 2;;
    --no-password) USE_PASSWORD="0"; shift;;
    --keep-mapping) KEEP_MAPPING="--keep-mapping"; shift;;
    *) shift;;
  esac
done

case "$cmd" in
  up)       cmd_up;;
  down)     cmd_down "${KEEP_MAPPING:-}";;
  status)   cmd_status;;
  url)      cmd_url;;
  password) cmd_password;;
  open)     cmd_open;;
  install)  cmd_install;;
  version|--version|-V) ok "headview v$HEADVIEW_VERSION";;
  ""|help|-h|--help) usage;;
  *) err "unknown command: $cmd"; hint "run 'headview help' for usage"; exit 1;;
esac
