#!/usr/bin/env bash
#
# pagewatch - a folder that publishes itself.
# Watches a static site folder and redeploys it to Cloudflare whenever it changes.
# Botslang Labs - botslang.com
#
set -euo pipefail

WATCH_DIR="${WATCH_DIR:-./site}"
PROJECT_NAME="${PROJECT_NAME:-botslang}"
DEPLOY_TARGET="${DEPLOY_TARGET:-pages}"
WORKER_NAME="${WORKER_NAME:-$PROJECT_NAME}"
COMPATIBILITY_DATE="${COMPATIBILITY_DATE:-2026-06-24}"
DEBOUNCE="${DEBOUNCE:-3}"
LIVE_URL="${LIVE_URL:-}"
SERVICE_NAME="${SERVICE_NAME:-pagewatch}"
PAGEWATCH_HOME="${PAGEWATCH_HOME:-$HOME/.pagewatch}"
SCRIPT_INSTALL_PATH="${SCRIPT_INSTALL_PATH:-$PAGEWATCH_HOME/pagewatch.sh}"
ENV_FILE="${ENV_FILE:-$PAGEWATCH_HOME/$SERVICE_NAME.env}"
LOG_FILE="${LOG_FILE:-$PAGEWATCH_HOME/$SERVICE_NAME.log}"
ERR_LOG_FILE="${ERR_LOG_FILE:-$PAGEWATCH_HOME/$SERVICE_NAME.err.log}"

log() { printf '%s  %s\n' "$(date '+%H:%M:%S')" "$*"; }

usage() {
  cat <<'MSG'
pagewatch - watch a static folder and publish it to Cloudflare.

Run once:
  WATCH_DIR=/path/to/site PROJECT_NAME=my-pages-project ./pagewatch.sh --once
  WATCH_DIR=/path/to/site DEPLOY_TARGET=worker WORKER_NAME=my-worker ./pagewatch.sh --once

Watch in this terminal:
  WATCH_DIR=/path/to/site PROJECT_NAME=my-pages-project LIVE_URL=https://example.com ./pagewatch.sh
  WATCH_DIR=/path/to/site DEPLOY_TARGET=worker WORKER_NAME=my-worker LIVE_URL=https://example.com ./pagewatch.sh

Install as a background service:
  WATCH_DIR=/absolute/path/to/site PROJECT_NAME=my-pages-project LIVE_URL=https://example.com ./pagewatch.sh --install-service
  WATCH_DIR=/absolute/path/to/site DEPLOY_TARGET=worker WORKER_NAME=my-worker LIVE_URL=https://example.com ./pagewatch.sh --install-service

Change how long PageWatch waits after file changes:
  DEBOUNCE=10 WATCH_DIR=/absolute/path/to/site PROJECT_NAME=my-pages-project LIVE_URL=https://example.com ./pagewatch.sh --install-service
  Re-run --install-service with a new DEBOUNCE value to update an existing service.

Manage the service:
  ./pagewatch.sh --status
  ./pagewatch.sh --uninstall-service

Notes:
  - DEPLOY_TARGET defaults to pages. Use DEPLOY_TARGET=worker for Cloudflare Worker static-assets sites.
  - DEBOUNCE defaults to 3 seconds. It means "wait for this many quiet seconds before deploying."
  - For Cloudflare Pages, create the project once with:
      wrangler pages project create my-pages-project
    If it asks for a production branch name, press Enter.
  - --install-service copies this script to ~/.pagewatch first, so it is safe even if you downloaded it to Downloads.
MSG
}

absolute_path() {
  case "$1" in
    /*) printf '%s\n' "$1" ;;
    *) printf '%s/%s\n' "$(pwd -P)" "$1" ;;
  esac
}

shell_quote() {
  printf "'%s'" "$(printf '%s' "$1" | sed "s/'/'\\\\''/g")"
}

write_env_file() {
  mkdir -p "$PAGEWATCH_HOME"
  local abs_watch_dir
  abs_watch_dir="$(absolute_path "$WATCH_DIR")"
  [ -d "$abs_watch_dir" ] || { echo "folder not found: $abs_watch_dir" >&2; exit 1; }

  {
    printf 'WATCH_DIR=%s\n' "$(shell_quote "$abs_watch_dir")"
    printf 'PROJECT_NAME=%s\n' "$(shell_quote "$PROJECT_NAME")"
    printf 'DEPLOY_TARGET=%s\n' "$(shell_quote "$DEPLOY_TARGET")"
    printf 'WORKER_NAME=%s\n' "$(shell_quote "$WORKER_NAME")"
    printf 'COMPATIBILITY_DATE=%s\n' "$(shell_quote "$COMPATIBILITY_DATE")"
    printf 'DEBOUNCE=%s\n' "$(shell_quote "$DEBOUNCE")"
    printf 'LIVE_URL=%s\n' "$(shell_quote "$LIVE_URL")"
    printf 'PAGEWATCH_HOME=%s\n' "$(shell_quote "$PAGEWATCH_HOME")"
    printf 'LOG_FILE=%s\n' "$(shell_quote "$LOG_FILE")"
    printf 'ERR_LOG_FILE=%s\n' "$(shell_quote "$ERR_LOG_FILE")"
  } > "$ENV_FILE"
}

install_self() {
  mkdir -p "$PAGEWATCH_HOME"
  cp "$0" "$SCRIPT_INSTALL_PATH"
  chmod +x "$SCRIPT_INSTALL_PATH"
}

wrangler_cmd() {
  if command -v wrangler >/dev/null 2>&1; then
    wrangler "$@"
  else
    npx --yes wrangler "$@"
  fi
}

check_common_requirements() {
  command -v node >/dev/null 2>&1 || { echo "node.js is required for wrangler" >&2; exit 1; }
  command -v curl >/dev/null 2>&1 || { echo "curl is required for live checks" >&2; exit 1; }
  if ! command -v wrangler >/dev/null 2>&1 && ! command -v npx >/dev/null 2>&1; then
    echo "wrangler or npx is required. Install wrangler with: npm i -g wrangler" >&2
    exit 1
  fi
  [ -d "$WATCH_DIR" ] || { echo "folder not found: $WATCH_DIR" >&2; exit 1; }
}

check_watcher_requirement() {
  if command -v fswatch >/dev/null 2>&1 || command -v inotifywait >/dev/null 2>&1; then
    return
  fi
  cat >&2 <<'MSG'
pagewatch needs a file watcher. Install one:
  macOS   brew install fswatch
  Ubuntu  sudo apt-get install inotify-tools
  Fedora  sudo dnf install inotify-tools
MSG
  exit 1
}

deploy() {
  case "$DEPLOY_TARGET" in
    pages)
      log "publishing $WATCH_DIR -> Cloudflare Pages project $PROJECT_NAME"
      wrangler_cmd --install-skills=false pages deploy "$WATCH_DIR" --project-name "$PROJECT_NAME"
      ;;
    worker|workers)
      log "publishing $WATCH_DIR -> Cloudflare Worker $WORKER_NAME"
      wrangler_cmd --install-skills=false deploy "$WATCH_DIR" \
        --name "$WORKER_NAME" \
        --compatibility-date "$COMPATIBILITY_DATE" \
        --keep-vars
      ;;
    *)
      echo "unknown DEPLOY_TARGET: $DEPLOY_TARGET" >&2
      echo "use DEPLOY_TARGET=pages or DEPLOY_TARGET=worker" >&2
      return 1
      ;;
  esac

  if [ -n "$LIVE_URL" ]; then
    if curl -fsSI "${LIVE_URL}?pagewatch=$(date +%s)" >/dev/null; then
      log "live - checked $LIVE_URL"
    else
      log "published, but live check failed for $LIVE_URL"
    fi
  else
    log "published - open the Cloudflare URL above, or set LIVE_URL=https://your-domain.com"
  fi
}

watch_stream() {
  if command -v fswatch >/dev/null 2>&1; then
    fswatch -o "$WATCH_DIR"
  elif command -v inotifywait >/dev/null 2>&1; then
    inotifywait -m -r -e modify,create,delete,move "$WATCH_DIR" 2>/dev/null
  fi
}

run_watch() {
  check_common_requirements
  check_watcher_requirement
  trap 'log "pagewatch stopped"; exit 0' INT TERM

  log "pagewatch up - watching $WATCH_DIR - target $DEPLOY_TARGET - debounce ${DEBOUNCE}s"
  deploy

  watch_stream | while read -r _; do
    while read -r -t "$DEBOUNCE" _; do :; done
    deploy
  done
}

install_macos_service() {
  local plist="$HOME/Library/LaunchAgents/com.$SERVICE_NAME.pagewatch.plist"
  mkdir -p "$HOME/Library/LaunchAgents"
  local work_dir
  work_dir="$(dirname "$PAGEWATCH_HOME")"

  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>Label</key>
  <string>com.$SERVICE_NAME.pagewatch</string>
  <key>ProgramArguments</key>
  <array>
    <string>/bin/bash</string>
    <string>-lc</string>
    <string>cd $(shell_quote "$work_dir") &amp;&amp; set -a &amp;&amp; source $(shell_quote "$ENV_FILE") &amp;&amp; set +a &amp;&amp; exec /bin/bash $(shell_quote "$SCRIPT_INSTALL_PATH")</string>
  </array>
  <key>RunAtLoad</key>
  <true/>
  <key>KeepAlive</key>
  <true/>
  <key>StandardOutPath</key>
  <string>$LOG_FILE</string>
  <key>StandardErrorPath</key>
  <string>$ERR_LOG_FILE</string>
</dict>
</plist>
EOF

  launchctl bootout "gui/$(id -u)/com.$SERVICE_NAME.pagewatch" >/dev/null 2>&1 || true
  launchctl bootstrap "gui/$(id -u)" "$plist"
  log "installed launchd service com.$SERVICE_NAME.pagewatch"
  log "logs: $LOG_FILE"
}

install_linux_service() {
  local unit_dir="$HOME/.config/systemd/user"
  local unit="$unit_dir/$SERVICE_NAME-pagewatch.service"
  mkdir -p "$unit_dir"

  cat > "$unit" <<EOF
[Unit]
Description=PageWatch static site publisher ($SERVICE_NAME)
After=network-online.target

[Service]
Type=simple
EnvironmentFile=$ENV_FILE
WorkingDirectory=$PAGEWATCH_HOME
ExecStart=/bin/bash $SCRIPT_INSTALL_PATH
Restart=always
RestartSec=5
StandardOutput=append:$LOG_FILE
StandardError=append:$ERR_LOG_FILE

[Install]
WantedBy=default.target
EOF

  systemctl --user daemon-reload
  systemctl --user enable --now "$SERVICE_NAME-pagewatch.service"
  log "installed systemd user service $SERVICE_NAME-pagewatch.service"
  log "logs: $LOG_FILE"
  if ! loginctl show-user "$USER" -p Linger 2>/dev/null | grep -q '=yes'; then
    log "optional: run 'loginctl enable-linger $USER' if you want it to run before login"
  fi
}

install_service() {
  check_common_requirements
  check_watcher_requirement
  write_env_file
  install_self

  case "$(uname -s)" in
    Darwin) install_macos_service ;;
    Linux) install_linux_service ;;
    *) echo "--install-service supports macOS launchd and Linux systemd user services" >&2; exit 1 ;;
  esac

  log "service installed for $WATCH_DIR"
  log "target: $DEPLOY_TARGET"
  log "check status with: $SCRIPT_INSTALL_PATH --status"
}

uninstall_service() {
  case "$(uname -s)" in
    Darwin)
      launchctl bootout "gui/$(id -u)/com.$SERVICE_NAME.pagewatch" >/dev/null 2>&1 || true
      rm -f "$HOME/Library/LaunchAgents/com.$SERVICE_NAME.pagewatch.plist"
      ;;
    Linux)
      systemctl --user disable --now "$SERVICE_NAME-pagewatch.service" >/dev/null 2>&1 || true
      rm -f "$HOME/.config/systemd/user/$SERVICE_NAME-pagewatch.service"
      systemctl --user daemon-reload >/dev/null 2>&1 || true
      ;;
    *) ;;
  esac
  log "service removed"
}

status_service() {
  case "$(uname -s)" in
    Darwin)
      launchctl print "gui/$(id -u)/com.$SERVICE_NAME.pagewatch" 2>/dev/null || {
        echo "pagewatch service is not installed or not running"
        exit 1
      }
      ;;
    Linux)
      systemctl --user status "$SERVICE_NAME-pagewatch.service"
      ;;
    *) echo "status is supported on macOS and Linux" >&2; exit 1 ;;
  esac
  [ -f "$LOG_FILE" ] && { echo; echo "latest log:"; tail -n 30 "$LOG_FILE"; }
  [ -s "$ERR_LOG_FILE" ] && { echo; echo "latest errors:"; tail -n 30 "$ERR_LOG_FILE"; }
}

case "${1:-}" in
  -h|--help) usage ;;
  --once)
    check_common_requirements
    deploy
    ;;
  --install-service) install_service ;;
  --uninstall-service) uninstall_service ;;
  --status) status_service ;;
  "") run_watch ;;
  *)
    echo "unknown option: $1" >&2
    usage >&2
    exit 1
    ;;
esac
