#!/usr/bin/env bash
#===============================================================================
#
#             FILE: panels.sh
#            USAGE: bash panels.sh
#
#      DESCRIPTION: installs two SketchyBar panels on a fresh macOS:
#                     - sketchybar (top bar, managed by brew services)
#                     - bottom_bar (bottom bar, a renamed copy of the sketchybar
#                       binary driven by its own LaunchAgent)
#                   It also installs the required fonts and deploys the panel
#                   configuration into ~/.config/{sketchybar,bottom_bar}.
#
#     REQUIREMENTS: macOS, homebrew, curl, tar
#
#       OS SUPPORT: macOS (Apple Silicon or Intel)
#
#           AUTHOR: Guto Carvalho, guto@bolha.io
#             SITE: https://gutocarvalho.net
#         MASTODON: @gutocarvalho@bolha.us
#         PIXELFED: @gutocarvalho@bolha.photos
#      WRITEFREELY: @gutocarvalho@bolha.blog
#       MATTERMOST: @gutocarvalho:mattermost.bolha.chat
#
#     ORGANIZATION: gutocarvalho.net, gcn.sh, bolha.io
#
#          CREATED: 2026-08-13
#          UPDATED: 2026-08-13
#             REPO: https://gitlab.gutocarvalho.net/open/install
#
#     CONTRIBUTORS: name, email, mastodon
#
#     The code is simple on purpose: we want it to be friendly for people
#     that are starting with shell language and programming logic.
#
#     This script does NOT need root. Homebrew must be installed first
#     (see brew.sh). It also builds the Objective-C wifiname helper (needs the
#     Xcode Command Line Tools); the Location permission is approved by hand once.
#
#===============================================================================

set -o nounset                              # treat unset variables as an error

## 1. CONSTANTS/VARIABLES ######################################################

# where to fetch the panel config from. The single source of truth is the
# 'mysketchybar' repo (which holds sketchybar/ and bottom_bar/ at its root).
# Set PANELS_SRC to a local directory (containing sketchybar/ and bottom_bar/)
# to deploy from disk instead of downloading the archive.
PANELS_ARCHIVE="${PANELS_ARCHIVE:-https://gitlab.gutocarvalho.net/open/mysketchybar/-/archive/main/mysketchybar-main.tar.gz}"
PANELS_SRC="${PANELS_SRC:-}"

SKETCHYBAR_APP_FONT_URL="https://github.com/kvndrsslr/sketchybar-app-font/releases/latest/download/sketchybar-app-font.ttf"
LEKTON_NERD_FONT_URL="https://github.com/ryanoasis/nerd-fonts/releases/latest/download/Lekton.zip"
# SF Pro (Apple): holds the SF Symbols the bars use (e.g. the Homebrew glyph
# 􀐛 in the bottom bar). These glyphs are NOT in any Nerd Font, so without SF
# Pro they render as empty boxes. Apple ships it as a .dmg wrapping a .pkg;
# install_sf_pro() extracts the .otf per-user with no admin (no sudo/cask).
SF_PRO_DMG_URL="https://devimages-cdn.apple.com/design/resources/download/SF-Pro.dmg"

## 2. HELPERS #################################################################

### abort: print an error message and stop the script
function abort() {
  echo -e "\nError: $1" >&2
  echo -e "Tip: run 'bash -x panels.sh' to see exactly what failed.\n" >&2
  exit 1
}

### warn: print a non-fatal warning and keep going
function warn() {
  echo -e "Warning: $1" >&2
}

### check_macos: this script only runs on macOS
function check_macos() {
  if [ "$(uname -s)" != "Darwin" ]; then
    abort "this script only runs on macOS."
  fi
}

### check_not_root: homebrew (and this script) must NOT run as root
function check_not_root() {
  if [ "$(id -u)" = "0" ]; then
    abort "do NOT run this as root. Run it as your normal user."
  fi
}

### need_cmd: make sure a required command exists
function need_cmd() {
  if ! command -v "$1" >/dev/null 2>&1; then
    abort "'$1' is required but was not found. Please install it first."
  fi
}

## 3. INSTALL FUNCTIONS #######################################################

### 3.1 install sketchybar and the fonts it uses
function install_sketchybar() {
  echo -e "\n>>> Installing sketchybar and fonts...\n"
  brew install felixkratz/formulae/sketchybar || abort "could not install sketchybar."
  mkdir -p "$HOME/Library/Fonts"
  # Fonts are a HARD dependency: without them the bar renders every glyph as a
  # box. Install per-user via direct download instead of the brew cask — on a
  # multi-user Mac the cask's Caskroom is owned by whoever installed Homebrew
  # (a second user can't read it), and 'brew install --cask' is a no-op that
  # never copies the font into the second user's ~/Library/Fonts.
  curl -fsSL "$LEKTON_NERD_FONT_URL" -o /tmp/Lekton.zip \
    && unzip -oq -j /tmp/Lekton.zip '*.ttf' -d "$HOME/Library/Fonts/" \
    && rm -f /tmp/Lekton.zip \
    || abort "could not install Lekton Nerd Font (the bar glyphs need it)."
  curl -fsSL "$SKETCHYBAR_APP_FONT_URL" -o "$HOME/Library/Fonts/sketchybar-app-font.ttf" \
    || abort "could not download sketchybar-app-font (the app icons need it)."
  install_sf_pro
  echo "sketchybar installed: $(sketchybar --version | head -n1)"
}

### 3.1b install SF Pro (Apple SF Symbols the bars use, e.g. the Homebrew glyph)
function install_sf_pro() {
  # The bars draw Apple SF Symbols that no Nerd Font contains; SF Pro provides
  # them. Skip if already present (this or /Library/Fonts). We do NOT use the
  # brew cask or 'installer': both need sudo and write to /Library/Fonts, which
  # breaks this script's "no root" promise. Instead mount Apple's dmg, fully
  # expand the .pkg inside it and copy the .otf out of its payload — per-user,
  # no admin. ~210MB download, so it is the slowest step of the install.
  if ls "$HOME/Library/Fonts/SF-Pro-Display-Regular.otf" >/dev/null 2>&1 \
     || ls "/Library/Fonts/SF-Pro-Display-Regular.otf" >/dev/null 2>&1; then
    echo "SF Pro already installed."
    return
  fi
  echo -e "\n>>> Installing SF Pro (~210MB, the SF Symbol icons need it)...\n"
  local tmp pkg
  tmp=$(mktemp -d)
  if ! curl -fsSL "$SF_PRO_DMG_URL" -o "$tmp/SF-Pro.dmg"; then
    warn "could not download SF Pro; the SF Symbol icons will show as boxes."
    rm -rf "$tmp"; return
  fi
  mkdir -p "$tmp/mnt" "$tmp/exp"
  if ! hdiutil attach "$tmp/SF-Pro.dmg" -nobrowse -readonly -mountpoint "$tmp/mnt" >/dev/null 2>&1; then
    warn "could not mount the SF Pro dmg; the SF Symbol icons will show as boxes."
    rm -rf "$tmp"; return
  fi
  pkg=$(find "$tmp/mnt" -maxdepth 1 -name '*.pkg' | head -n1)
  if [ -n "$pkg" ] && pkgutil --expand-full "$pkg" "$tmp/exp/pkg" >/dev/null 2>&1; then
    find "$tmp/exp" -name '*.otf' -exec cp -f {} "$HOME/Library/Fonts/" \;
    echo "SF Pro installed ($(ls "$HOME/Library/Fonts" | grep -c 'SF-Pro') faces)."
  else
    warn "could not extract SF Pro from the pkg; the SF Symbol icons will show as boxes."
  fi
  hdiutil detach "$tmp/mnt" >/dev/null 2>&1
  rm -rf "$tmp"
}

### 3.2 fetch the panel config into a temporary directory, echo its path
function fetch_config() {
  if [ -n "$PANELS_SRC" ]; then
    [ -d "$PANELS_SRC/sketchybar" ] && [ -d "$PANELS_SRC/bottom_bar" ] \
      || abort "PANELS_SRC '$PANELS_SRC' must contain sketchybar/ and bottom_bar/."
    echo "$PANELS_SRC"
    return
  fi
  local tmp src
  tmp=$(mktemp -d)
  curl -fsSL "$PANELS_ARCHIVE" | tar xz -C "$tmp" || abort "could not download/extract the config archive."
  # the mysketchybar archive extracts to <repo>-<ref>/ with sketchybar/ and
  # bottom_bar/ at its root — find the dir that holds the top-bar config.
  src=$(find "$tmp" -maxdepth 2 -type d -name sketchybar | head -n1)
  src=${src%/sketchybar}
  [ -n "$src" ] && [ -d "$src/sketchybar" ] && [ -d "$src/bottom_bar" ] \
    || abort "could not find the sketchybar/ and bottom_bar/ config inside the archive."
  echo "$src"
}

### 3.3 deploy one config dir into ~/.config, backing up any existing one
function deploy_config_dir() {
  local src="$1" name="$2" target="$HOME/.config/$2"
  if [ -e "$target" ]; then
    local backup
    backup="$target.bak-$(date +%s)"
    mv "$target" "$backup"
    warn "existing ~/.config/$name moved to $backup"
  fi
  mkdir -p "$HOME/.config"
  cp -R "$src/$name" "$target"
  # make every script and the rc executable
  find "$target" -type f -name '*.sh' -exec chmod +x {} +
  [ -f "$target/sketchybarrc" ] && chmod +x "$target/sketchybarrc"
  echo "deployed ~/.config/$name"
}

### 3.4 create the bottom_bar binary (a renamed copy of sketchybar) + LaunchAgent
function install_bottom_bar() {
  echo -e "\n>>> Setting up the bottom_bar (second bar)...\n"
  local prefix bin plist logdir
  prefix="$(brew --prefix)"
  # per-user paths: putting the binary/logs under $prefix (e.g. /opt/homebrew,
  # owned by whoever installed Homebrew) breaks on multi-user Macs, where a
  # second user cannot overwrite the first user's bottom_bar. Keep it in $HOME.
  bin="$HOME/.local/bin/bottom_bar"
  plist="$HOME/Library/LaunchAgents/homebrew.mxcl.bottom_bar.plist"
  logdir="$HOME/Library/Logs/bottom_bar"

  mkdir -p "$HOME/.local/bin" "$logdir" "$HOME/Library/LaunchAgents"
  cp -f "$prefix/bin/sketchybar" "$bin" || abort "could not copy the sketchybar binary."
  codesign --force -s - "$bin" 2>/dev/null || warn "could not codesign bottom_bar (it may still work)."

  cat > "$plist" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>EnvironmentVariables</key>
	<dict>
		<key>LANG</key>
		<string>en_US.UTF-8</string>
		<key>PATH</key>
		<string>$HOME/.local/bin:$prefix/bin:$prefix/sbin:/usr/bin:/bin:/usr/sbin:/sbin</string>
	</dict>
	<key>KeepAlive</key>
	<true/>
	<key>Label</key>
	<string>homebrew.mxcl.bottom_bar</string>
	<key>ProcessType</key>
	<string>Interactive</string>
	<key>ProgramArguments</key>
	<array>
		<string>$bin</string>
	</array>
	<key>RunAtLoad</key>
	<true/>
	<key>StandardErrorPath</key>
	<string>$logdir/bottom_bar.err.log</string>
	<key>StandardOutPath</key>
	<string>$logdir/bottom_bar.out.log</string>
</dict>
</plist>
EOF
  echo "bottom_bar binary and LaunchAgent installed."
}

### 3.5 build and prime the Wi-Fi SSID helper (Objective-C .app bundle)
# On macOS 14.4+ the SSID is hidden unless the process has Location permission.
# The helper is a tiny signed .app that owns that permission; build.sh compiles
# it and get-ssid.sh launches it via LaunchServices so TCC attributes Location
# to the bundle (running the raw binary would attribute it to the terminal).
function install_wifiname() {
  echo -e "\n>>> Building the Wi-Fi SSID helper (wifiname)...\n"
  local helper="$HOME/.config/sketchybar/helpers/wifiname"
  if [ ! -f "$helper/build.sh" ]; then
    warn "wifiname helper not found in the config, skipping."
    return
  fi
  if ! command -v clang >/dev/null 2>&1; then
    warn "clang not found (Xcode Command Line Tools are missing)."
    warn "Install them with 'xcode-select --install', then run: bash $helper/build.sh"
    return
  fi
  bash "$helper/build.sh" || { warn "wifiname build failed; the SSID will be blank."; return; }
  # first launch triggers the macOS Location permission prompt (approve it once)
  echo -e "\n>>> Priming the helper (please APPROVE the Location prompt if it shows up)...\n"
  "$helper/get-ssid.sh" >/dev/null 2>&1 || true
  echo "wifiname helper built."
}

### 3.6 (re)start both bars
function start_panels() {
  echo -e "\n>>> Starting both bars...\n"
  brew services restart sketchybar || warn "could not start sketchybar service."
  local plist="$HOME/Library/LaunchAgents/homebrew.mxcl.bottom_bar.plist"
  launchctl bootout "gui/$(id -u)" "$plist" 2>/dev/null
  launchctl bootstrap "gui/$(id -u)" "$plist" 2>/dev/null \
    || launchctl load -w "$plist" 2>/dev/null \
    || warn "could not load the bottom_bar LaunchAgent."
}

## 4. EXECUTING ###############################################################

check_macos
check_not_root
need_cmd brew
need_cmd curl
need_cmd tar

install_sketchybar

CONFIG_SRC=$(fetch_config)
deploy_config_dir "$CONFIG_SRC" sketchybar
deploy_config_dir "$CONFIG_SRC" bottom_bar

install_bottom_bar
install_wifiname
start_panels

## 5. NOTIFICATIONS ###########################################################

echo -e "\n----------------------------------------------------------------------------------"
echo -e "\nAll done! Your two panels are up:"
echo "  - top bar:    sketchybar   (brew services)"
echo "  - bottom bar: bottom_bar   (LaunchAgent)"
echo -e "\nConfig lives in ~/.config/sketchybar and ~/.config/bottom_bar."
echo "Reload after edits with:  sketchybar --reload   /   bottom_bar --reload"
echo -e "\nWi-Fi SSID helper: if the network name shows blank, approve Location for"
echo "'WifiName' in System Settings > Privacy & Security > Location Services,"
echo "then run:  sketchybar --reload"
echo -e "\nEnjoy it!"
echo -e "\nWant to know more about our collective? Visit https://bolha.io and consider joining us! ;)"
echo -e "Come and visit our mastodon instance:    https://bolha.us"
echo -e "Come and visit our mattermost instance:  https://mattermost.bolha.chat"
echo -e "Say hello on the fediverse:              @gutocarvalho@bolha.us"
echo -e "\n----------------------------------------------------------------------------------\n"
