#!/usr/bin/env bash
set -euo pipefail
source "$(dirname "$0")/_beagle-racket"

_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/var/tmp}"
PORTFILE="${BEAGLE_DAEMON_PORTFILE:-${_RUNTIME_DIR}/beagle-daemon.port}"
PIDFILE="${BEAGLE_DAEMON_PIDFILE:-${_RUNTIME_DIR}/beagle-daemon.pid}"
export BEAGLE_DAEMON_PORTFILE="$PORTFILE"

usage() {
    cat >&2 <<'EOF'
usage: beagle-daemon <command> [args...]

Commands:
  start           Start daemon in background (TCP, ephemeral port)
  stop            Stop running daemon
  status          Check if daemon is running (JSON)
  query CMD ARGS  Send query to running daemon (falls back to one-shot)

Queries (same as CLI tools but returns JSON):
  sig <fn-name> <dir>
  fields <record> <dir>
  callers <fn-name> <dir>
  provides <file>
  impact <fn-name> <dir>
  check <dir>
  check-enriched <dir>        Full type check + enriched context
  check-result [file]         Latest pre-computed result from watcher
  latest-results              All results since last query (clears buffer)
  watch <dir>                 Start inotify watcher on directory
  unwatch                     Stop all watchers
  invalidate [file]
  ping

The daemon keeps parsed ASTs cached, eliminating Racket startup (0.33s)
and re-parse (1.6s) costs per tool call. ~60 queries/session → saves ~2min.

Environment:
  BEAGLE_DAEMON_PORTFILE  Port file (default: $XDG_RUNTIME_DIR/beagle-daemon.port)
  BEAGLE_DAEMON_PIDFILE   PID file (default: $XDG_RUNTIME_DIR/beagle-daemon.pid)
EOF
    exit 2
}

is_running() {
    [[ -f "$PIDFILE" ]] && kill -0 "$(cat "$PIDFILE")" 2>/dev/null
}

get_port() {
    if [[ -f "$PORTFILE" ]]; then
        cat "$PORTFILE"
    else
        echo ""
    fi
}

send_query() {
    local port
    port=$(get_port)
    if [[ -z "$port" ]] || ! is_running; then
        # Fallback: one-shot mode (no persistent daemon)
        echo -e "$*\nquit" | "$RACKET" -e '(require beagle/private/daemon) (run-daemon)' 2>/dev/null | head -1
        return
    fi
    # Send via TCP
    exec 3<>/dev/tcp/127.0.0.1/"$port"
    echo "$*" >&3
    local response
    read -r response <&3
    exec 3>&-
    echo "$response"
}

cmd_start() {
    if is_running; then
        echo "Daemon already running (pid $(cat "$PIDFILE"), port $(get_port))" >&2
        exit 0
    fi

    rm -f "$PORTFILE" "$PIDFILE"

    "$RACKET" -e '(require beagle/private/daemon) (run-daemon-tcp)' &
    local pid=$!
    echo "$pid" > "$PIDFILE"
    chmod 0600 "$PIDFILE"

    # Wait for port file to appear. Cold-start cost is dominated by
    # `(require beagle/private/daemon)` expansion: ~0.5s with compiled
    # bytecode (compiled/daemon_rkt.zo present), ~8-10s without. 30s
    # covers a clean checkout, a stale-bytecode rebuild, and slower
    # machines without ever masking a real failure.
    local timeout_tenths="${BEAGLE_DAEMON_START_TIMEOUT_TENTHS:-300}"
    local waited=0
    while [[ ! -f "$PORTFILE" ]] && [[ $waited -lt $timeout_tenths ]]; do
        if ! kill -0 "$pid" 2>/dev/null; then
            echo "Daemon process died during startup (pid $pid)." >&2
            echo "Try: $RACKET -e '(require beagle/private/daemon) (run-daemon-tcp)' to see the error." >&2
            rm -f "$PIDFILE"
            exit 1
        fi
        sleep 0.1
        waited=$((waited + 1))
    done

    if [[ ! -f "$PORTFILE" ]]; then
        echo "Daemon failed to start (no port file after $((timeout_tenths / 10))s)." >&2
        echo "Hint: precompile bytecode with: raco make beagle-lib/private/daemon.rkt" >&2
        kill "$pid" 2>/dev/null || true
        rm -f "$PIDFILE"
        exit 1
    fi

    local port
    port=$(get_port)
    echo "Daemon started (pid $pid, port $port)" >&2
}

cmd_stop() {
    if ! is_running; then
        echo "Daemon not running" >&2
        rm -f "$PIDFILE" "$PORTFILE"
        exit 0
    fi

    local pid
    pid=$(cat "$PIDFILE")

    # Try graceful quit first
    local port
    port=$(get_port)
    if [[ -n "$port" ]]; then
        (exec 3<>/dev/tcp/127.0.0.1/"$port" && echo "quit" >&3 && exec 3>&-) 2>/dev/null || true
        sleep 0.2
    fi

    # Force if still alive
    if kill -0 "$pid" 2>/dev/null; then
        kill "$pid" 2>/dev/null || true
    fi

    rm -f "$PIDFILE" "$PORTFILE"
    echo "Daemon stopped (pid $pid)" >&2
}

cmd_status() {
    if is_running; then
        send_query "ping"
    else
        echo '{"ok":false,"status":"stopped"}'
        rm -f "$PIDFILE" "$PORTFILE"
    fi
}

if [[ $# -lt 1 ]]; then
    usage
fi

case "$1" in
    start)
        shift
        cmd_start
        # If --watch <dir> was passed, start watching after daemon is up
        if [[ ${1:-} == "--watch" ]] && [[ -n ${2:-} ]]; then
            send_query "watch" "$2"
        fi
        ;;
    stop)    cmd_stop ;;
    status)  cmd_status ;;
    query)   shift; send_query "$@" ;;
    -h|--help) usage ;;
    *)       usage ;;
esac
