#!/bin/bash

# Priority Forge - Setup & Management Script
# This script bootstraps the development environment and manages the full stack

set -e

# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
BOLD='\033[1m'
NC='\033[0m' # No Color

# Get script directory (where setup.sh lives)
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PID_DIR="$SCRIPT_DIR/.pids"

print_header() {
    echo ""
    echo -e "${BLUE}╔════════════════════════════════════════╗${NC}"
    echo -e "${BLUE}║       Priority Forge Setup             ║${NC}"
    echo -e "${BLUE}╚════════════════════════════════════════╝${NC}"
    echo ""
}

print_step() {
    echo -e "${BLUE}[STEP]${NC} $1"
}

print_success() {
    echo -e "${GREEN}[OK]${NC} $1"
}

print_warning() {
    echo -e "${YELLOW}[WARN]${NC} $1"
}

print_error() {
    echo -e "${RED}[ERROR]${NC} $1"
}

print_info() {
    echo -e "${CYAN}[INFO]${NC} $1"
}

show_help() {
    echo -e "${BOLD}Priority Forge - Setup & Management Script${NC}"
    echo ""
    echo -e "${BOLD}Usage:${NC}"
    echo "  ./setup.sh [command] [--no-service]"
    echo ""
    echo -e "${BOLD}Commands:${NC}"
    echo "  install                Full installation (deps, database, MCP config, persistent service)"
    echo "  start                  Start the full stack (backend + frontend)"
    echo "  start:backend          Start only the backend server"
    echo "  start:frontend         Start only the frontend dev server"
    echo "  stop                   Stop all running servers"
    echo "  status                 Check if servers are running"
    echo "  logs                   Show recent logs from running servers"
    echo "  verify                 Verify the setup is working correctly"
    echo "  install-systemd        Install systemd user services (Linux)"
    echo "  install-launchd        Install launchd agents (macOS)"
    echo "  install-cursor-plugin  Copy cursor-plugin/ bundle to ~/.cursor/plugins/priority-forge/"
    echo "  help                   Show this help message"
    echo ""
    echo -e "${BOLD}Flags:${NC}"
    echo "  --no-service       Skip persistent-service install during 'install' (useful on CI / headless boxes)"
    echo ""
    echo -e "${BOLD}Examples:${NC}"
    echo "  ./setup.sh                        # Full install incl. persistent service"
    echo "  ./setup.sh install --no-service   # Install without systemd/launchd (CI-style)"
    echo "  ./setup.sh install-cursor-plugin  # Install the Cursor plugin bundle"
    echo "  ./setup.sh start                  # Start both backend and frontend"
    echo "  ./setup.sh stop                   # Stop all servers"
    echo ""
    echo -e "${BOLD}Environment:${NC}"
    echo "  CI=1               Same as --no-service (auto-detected headless)"
    echo ""
    echo -e "${BOLD}Ports:${NC}"
    echo "  Backend:  http://localhost:3456"
    echo "  Frontend: http://localhost:5173 (auto-increments to 5174+ if 5173 is busy)"
    echo ""
}

# Detect OS
detect_os() {
    case "$(uname -s)" in
        Darwin*)    echo "macos" ;;
        Linux*)     echo "linux" ;;
        CYGWIN*|MINGW*|MSYS*) echo "windows" ;;
        *)          echo "unknown" ;;
    esac
}

# Detect WSL (Windows Subsystem for Linux). Returns 0 if running under WSL.
# Checks both /proc/version (WSL1 + WSL2) and $WSL_DISTRO_NAME (WSL2 only),
# so we catch every variant including older WSL1 boxes without the env var.
detect_wsl() {
    if [ -n "${WSL_DISTRO_NAME:-}" ] || [ -n "${WSL_INTEROP:-}" ]; then
        return 0
    fi
    if [ -r /proc/version ] && grep -qiE "microsoft|wsl" /proc/version 2>/dev/null; then
        return 0
    fi
    return 1
}

# Refuse to operate on the Windows-mounted filesystem from inside WSL.
# SQLite writes through DrvFs are ~100x slower than ext4 and chokidar
# file-watching is unreliable; running the stack from /mnt/c/ produces silent
# data corruption + missed reloads. Set PRIORITY_FORGE_ALLOW_CROSS_MOUNT=1 to
# override (CI / read-only smoke tests only).
check_cross_mount() {
    local pwd_path="${1:-$PWD}"
    if [ "${PRIORITY_FORGE_ALLOW_CROSS_MOUNT:-0}" = "1" ]; then
        return 0
    fi
    case "$pwd_path" in
        /mnt/[a-z]/*|/mnt/[A-Z]/*)
            print_error "priority-forge is checked out on a Windows-mounted path:"
            echo "    $pwd_path"
            echo ""
            echo "SQLite writes and file watching are unreliable on /mnt/<drive>/"
            echo "(DrvFs). The stack will appear to install but data writes and"
            echo "auto-reloads will fail intermittently."
            echo ""
            echo "Fix: move the repo into your WSL home filesystem:"
            echo "    mv \"$pwd_path\" ~/priority-forge"
            echo "    cd ~/priority-forge"
            echo "    ./setup.sh install"
            echo ""
            echo "Override (NOT recommended): PRIORITY_FORGE_ALLOW_CROSS_MOUNT=1 ./setup.sh ..."
            exit 1
            ;;
    esac
    return 0
}

# Ensure PID directory exists
ensure_pid_dir() {
    mkdir -p "$PID_DIR"
}

# Check if a process is running by PID file
is_running() {
    local pid_file="$1"
    if [ -f "$pid_file" ]; then
        local pid=$(cat "$pid_file")
        if ps -p "$pid" > /dev/null 2>&1; then
            return 0
        fi
    fi
    return 1
}

# Get PID from file
get_pid() {
    local pid_file="$1"
    if [ -f "$pid_file" ]; then
        cat "$pid_file"
    fi
}

# Resolve the port the frontend is actually bound to.
#
# Vite has no `strictPort` set in frontend/vite.config.ts, so when 5173 is in
# use it auto-increments to 5174, 5175, ... Hardcoding 5173 in our logs makes
# the onboarding output lie when something else is already on 5173 (a stale
# vite, another project's dev server, etc.) — see
# https://vite.dev/config/server-options#server-port.
#
# Resolution order:
#   1. Listening TCP port of the given PID (most reliable).
#   2. Vite's "Local:  http://localhost:PORT/" banner from $PID_DIR/frontend.log.
#   3. Fallback to the default 5173.
#
# Args:  $1 = pid (optional), $2 = log file (optional)
# Echoes: a port number. Always succeeds (falls back to 5173).
detect_frontend_port() {
    local pid="${1:-}"
    local log_file="${2:-$PID_DIR/frontend.log}"
    local port=""

    if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
        if command -v ss >/dev/null 2>&1; then
            # Walk the pid + every descendant — `npm run dev` forks `sh -c vite`
            # which forks the actual `node .../vite` that owns the socket, so
            # ss queried against just the top pid finds nothing.
            local pids="$pid"
            if command -v pgrep >/dev/null 2>&1; then
                local descendants
                descendants=$(pgrep -P "$pid" 2>/dev/null)
                for child in $descendants; do
                    pids="$pids $child"
                    local grandchildren
                    grandchildren=$(pgrep -P "$child" 2>/dev/null)
                    pids="$pids $grandchildren"
                done
            fi
            for p in $pids; do
                port=$(ss -tlnp 2>/dev/null | grep "pid=$p," | grep -oE '127\.0\.0\.1:[0-9]+|0\.0\.0\.0:[0-9]+|\[::\]:[0-9]+|\[::1\]:[0-9]+' | grep -oE '[0-9]+$' | head -1)
                if [ -n "$port" ]; then
                    echo "$port"
                    return 0
                fi
            done
        elif command -v lsof >/dev/null 2>&1; then
            # macOS: `lsof -R` is the equivalent of walking descendants, but the
            # simpler approach is to ask lsof for any listening TCP socket that
            # belongs to a process whose ancestry includes our pid.
            local descendants
            descendants=$(pgrep -P "$pid" 2>/dev/null)
            local pids="$pid $descendants"
            for child in $descendants; do
                local grandchildren
                grandchildren=$(pgrep -P "$child" 2>/dev/null)
                pids="$pids $grandchildren"
            done
            for p in $pids; do
                port=$(lsof -nP -p "$p" -iTCP -sTCP:LISTEN 2>/dev/null | awk 'NR>1 {print $9}' | grep -oE '[0-9]+$' | head -1)
                if [ -n "$port" ]; then
                    echo "$port"
                    return 0
                fi
            done
        fi
    fi

    if [ -f "$log_file" ]; then
        # Vite's banner (handles ANSI color codes between "Local:" and the URL):
        #   ➜  Local:   http://localhost:5174/
        port=$(grep -oE 'Local:[^0-9]*[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+:[0-9]+|Local:[^0-9]*localhost:[0-9]+' "$log_file" 2>/dev/null \
            | grep -oE ':[0-9]+' | tr -d ':' | tail -1)
        if [ -n "$port" ]; then
            echo "$port"
            return 0
        fi
    fi

    echo "5173"
}

# Check prerequisites (Node.js, npm)
check_prerequisites() {
    local OS=$(detect_os)

    # Native Windows shell (Git Bash / MSYS / Cygwin) — setup.sh is bash-only
    # and the persistent-service layer is systemd/launchd. Direct the user
    # into WSL where the Linux path works as-is.
    if [ "$OS" = "windows" ]; then
        print_error "Native Windows (Git Bash / MSYS / Cygwin) is not supported."
        echo ""
        echo "Run this script from inside WSL (Ubuntu) instead:"
        echo "    1. Open Ubuntu (WSL) from the Start menu, OR run 'wsl' from PowerShell"
        echo "    2. Clone the repo INTO your WSL home (NOT /mnt/c/):"
        echo "         git clone https://github.com/Unobtainiumrock/priority-forge.git ~/priority-forge"
        echo "         cd ~/priority-forge"
        echo "    3. ./setup.sh install"
        echo ""
        echo "WSL install guide: https://docs.microsoft.com/en-us/windows/wsl/install"
        echo "Experimental native-Windows scaffolding lives in ./windows/ — see windows/README.md"
        exit 1
    fi

    if [ "$OS" = "unknown" ]; then
        print_error "Unknown operating system. This script supports macOS and Linux (including WSL)."
        exit 1
    fi

    # WSL: cross-mount guard. Even when detect_os returns "linux", the repo
    # might be on /mnt/c/. Block that before any install/start side effects.
    if detect_wsl; then
        IS_WSL=1
        print_info "Detected WSL${WSL_DISTRO_NAME:+ ($WSL_DISTRO_NAME)} — treating as Linux."
        check_cross_mount "$SCRIPT_DIR"
    fi

    # Check for Node.js
    NODE_INSTALLED=false
    NODE_VERSION=""

    if command -v node &> /dev/null; then
        NODE_INSTALLED=true
        NODE_VERSION=$(node --version)
    else
        # Check if nvm is installed
        if [ -d "$HOME/.nvm" ] || command -v nvm &> /dev/null; then
            export NVM_DIR="$HOME/.nvm"
            [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
            
            if command -v node &> /dev/null; then
                NODE_INSTALLED=true
                NODE_VERSION=$(node --version)
            fi
        fi
    fi

    if [ "$NODE_INSTALLED" = false ]; then
        print_error "Node.js is required but not found."
        echo "Please install Node.js (v18+) and run this script again."
        echo "  - nvm: https://github.com/nvm-sh/nvm"
        echo "  - macOS: brew install node"
        echo "  - Linux: sudo apt install nodejs npm"
        exit 1
    fi

    # Check Node version (need 18+)
    NODE_MAJOR=$(echo $NODE_VERSION | cut -d'.' -f1 | tr -d 'v')
    if [ "$NODE_MAJOR" -lt 18 ]; then
        print_warning "Node.js version $NODE_VERSION is below recommended (v18+)."
    fi

    # Check for npm
    if ! command -v npm &> /dev/null; then
        print_error "npm not found. Please install npm and run this script again."
        exit 1
    fi

    return 0
}

# Full installation
do_install() {
    local OS=$(detect_os)
    
    print_header
    echo "Detected OS: $OS"
    echo ""

    # Step 1: Check for Homebrew (macOS only)
    print_step "Checking prerequisites..."

    if [ "$OS" = "macos" ]; then
        if ! command -v brew &> /dev/null; then
            print_warning "Homebrew not found."
            echo "Homebrew is the recommended package manager for macOS."
            read -p "Install Homebrew? (y/n) " -n 1 -r
            echo
            if [[ $REPLY =~ ^[Yy]$ ]]; then
                print_step "Installing Homebrew..."
                /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
                
                # Add brew to PATH for this session
                if [ -f "/opt/homebrew/bin/brew" ]; then
                    eval "$(/opt/homebrew/bin/brew shellenv)"
                elif [ -f "/usr/local/bin/brew" ]; then
                    eval "$(/usr/local/bin/brew shellenv)"
                fi
                print_success "Homebrew installed"
            else
                print_warning "Skipping Homebrew. You'll need to install Node.js manually."
            fi
        else
            print_success "Homebrew found"
        fi
    fi

    # Step 2: Check for Node.js
    NODE_INSTALLED=false
    NODE_VERSION=""

    if command -v node &> /dev/null; then
        NODE_INSTALLED=true
        NODE_VERSION=$(node --version)
        print_success "Node.js found: $NODE_VERSION"
    else
        print_warning "Node.js not found."
        
        # Check if nvm is installed
        if [ -d "$HOME/.nvm" ] || command -v nvm &> /dev/null; then
            print_step "nvm detected. Loading nvm..."
            export NVM_DIR="$HOME/.nvm"
            [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
            
            if command -v node &> /dev/null; then
                NODE_INSTALLED=true
                NODE_VERSION=$(node --version)
                print_success "Node.js found via nvm: $NODE_VERSION"
            else
                print_step "Installing Node.js via nvm..."
                nvm install --lts
                nvm use --lts
                NODE_INSTALLED=true
                NODE_VERSION=$(node --version)
                print_success "Node.js installed via nvm: $NODE_VERSION"
            fi
        elif [ "$OS" = "macos" ] && command -v brew &> /dev/null; then
            read -p "Install Node.js via Homebrew? (y/n) " -n 1 -r
            echo
            if [[ $REPLY =~ ^[Yy]$ ]]; then
                print_step "Installing Node.js via Homebrew..."
                brew install node
                NODE_INSTALLED=true
                NODE_VERSION=$(node --version)
                print_success "Node.js installed: $NODE_VERSION"
            fi
        elif [ "$OS" = "linux" ]; then
            echo "Please install Node.js using your package manager or nvm:"
            echo "  - nvm: https://github.com/nvm-sh/nvm"
            echo "  - apt: sudo apt install nodejs npm"
            echo "  - dnf: sudo dnf install nodejs npm"
            exit 1
        fi
    fi

    if [ "$NODE_INSTALLED" = false ]; then
        print_error "Node.js is required but not installed. Please install it and run this script again."
        exit 1
    fi

    # Check Node version (need 18+)
    NODE_MAJOR=$(echo $NODE_VERSION | cut -d'.' -f1 | tr -d 'v')
    if [ "$NODE_MAJOR" -lt 18 ]; then
        print_warning "Node.js version $NODE_VERSION is below recommended (v18+)."
        echo "Some features may not work correctly."
    fi

    # Step 3: Check for npm
    if ! command -v npm &> /dev/null; then
        print_error "npm not found. Please install npm and run this script again."
        exit 1
    fi
    print_success "npm found: $(npm --version)"

    # Step 4: Install backend dependencies
    print_step "Installing backend dependencies..."
    cd "$SCRIPT_DIR"
    npm install
    print_success "Backend dependencies installed"

    # Step 5: Install frontend dependencies
    print_step "Installing frontend dependencies..."
    cd "$SCRIPT_DIR/frontend"
    npm install
    cd "$SCRIPT_DIR"
    print_success "Frontend dependencies installed"

    # Step 6: Initialize database if needed
    print_step "Checking database..."
    if [ -f "$SCRIPT_DIR/data/progress.json" ]; then
        print_success "Database already exists (skipping seed)"
    else
        print_step "Creating initial database..."
        npx tsx scripts/seed.ts
        print_success "Database initialized"
    fi

    # Step 7: Configure MCP (interactive)
    #
    # Wrapped to NOT abort the rest of setup.sh if MCP config partially
    # fails — historically a single bad path in Claude Code's project
    # history (e.g. an entry resolving to '/') would EROFS-crash the
    # script and leave build/launchd/verify unfinished. configure-mcp.ts
    # now has per-write try/catch internally, but this is the
    # belt-and-suspenders so a NEW failure mode (or any other config
    # error) doesn't undo a working backend install.
    echo ""
    print_step "Configuring MCP integration..."
    if ! npx tsx scripts/configure-mcp.ts; then
        echo ""
        echo -e "${YELLOW}⚠ MCP configuration step exited non-zero — continuing with the rest of setup.${NC}"
        echo -e "${YELLOW}  Re-run later via:  npm run setup:mcp${NC}"
        echo ""
    fi

    # Step 8: Install persistent service by default (survives terminal/VS Code death and reboots).
    # Skipped automatically on CI/headless boxes or when --no-service is passed.
    echo ""
    ensure_persistent_services "$OS"

    # Step 9: Verify setup
    echo ""
    print_step "Verifying setup..."
    npx tsx scripts/verify.ts

    echo ""
    echo -e "${GREEN}╔════════════════════════════════════════╗${NC}"
    echo -e "${GREEN}║       Setup Complete!                  ║${NC}"
    echo -e "${GREEN}╚════════════════════════════════════════╝${NC}"
    echo ""
    echo "Next steps:"
    if [ "$PERSISTENT_INSTALLED" = true ]; then
        echo "  1. Services are already running as background daemons."
        echo "  2. Restart your AI tool (Cursor/Droid/Claude Code/Codex)"
        echo "  3. The AI will now track tasks automatically!"
    else
        echo "  1. Start the full stack:  ./setup.sh start"
        echo "     (uses nohup so the backend survives terminal/editor close)"
        echo "  2. Or start individually:"
        echo "     - Backend only:  ./setup.sh start:backend"
        echo "     - Frontend only: ./setup.sh start:frontend"
        echo "  3. Restart your AI tool (Cursor/Droid/Claude Code/Codex)"
        echo "  4. The AI will now track tasks automatically!"
        echo ""
        print_warning "Persistent services were not installed. Backend will not survive reboot."
        print_warning "Run './setup.sh install-systemd' (Linux) or './setup.sh install-launchd' (macOS) to enable."
    fi
    echo ""
    echo "Useful commands:"
    echo "  ./setup.sh status  - Check if servers are running"
    echo "  ./setup.sh stop    - Stop all servers"
    echo "  ./setup.sh help    - Show all available commands"
    echo ""
    echo -e "${BOLD}Memory hub (optional, off by default):${NC}"
    echo "  Local-only smoke:  npm run test:hub-smoke"
    echo "  Enable in agent:   hub_configure({ memory: { enabled: true, backend: 'dryrun',"
    echo "                                     teamId: 'team_x', agentId: 'you@team' },"
    echo "                                     orchestration: { enabled: true } })"
    echo "  Team Postgres:     docs/HUB_DEPLOYMENT.md  (provisions VPC + RDS + tokens)"
    echo "  Inspect planes:    hub_status   (pulse / memory / orchestration / warnings)"
    echo "  Canonical config:  ~/.priority-forge/team-hub.json  (mode 0600)"
    echo ""
}

# Install persistent background services by default so the stack survives
# terminal close, editor crashes, and reboots. Skipped automatically when:
#   - NO_SERVICE=1 is set (via --no-service flag)
#   - CI=1 is set (CI / headless environments)
#   - the OS isn't supported for persistent services (e.g. WSL without systemd user)
# Sets PERSISTENT_INSTALLED so do_install can tailor its next-step hints.
PERSISTENT_INSTALLED=false
ensure_persistent_services() {
    local os="$1"

    if [ "${NO_SERVICE:-0}" = "1" ] || [ "${CI:-0}" = "1" ]; then
        print_info "Skipping persistent-service install (--no-service or CI=1). Run './setup.sh install-systemd' or 'install-launchd' later."
        return 0
    fi

    case "$os" in
        linux)
            echo -e "${BOLD}Installing persistent background services (systemd user units)${NC}"
            echo "These run the backend, frontend, and watchdog independently of any"
            echo "shell, survive editor/terminal close, and auto-start on boot."
            echo "Pass --no-service to skip."
            echo ""
            if bash "$SCRIPT_DIR/scripts/install-systemd.sh" install; then
                PERSISTENT_INSTALLED=true
                print_success "Persistent services installed"
            else
                print_warning "systemd install failed. Run './setup.sh install-systemd' later to retry."
            fi
            ;;
        macos)
            echo -e "${BOLD}Installing persistent background services (launchd agents)${NC}"
            echo "These run the backend, frontend, and watchdog independently of any"
            echo "shell, survive editor/terminal close, and auto-start on login."
            echo "Pass --no-service to skip."
            echo ""
            if install_launchd_agents; then
                PERSISTENT_INSTALLED=true
                print_success "Persistent services installed"
            else
                print_warning "launchd install failed. See launchd/README.md to retry."
            fi
            ;;
        *)
            print_info "Persistent services not supported on this OS; skipping."
            ;;
    esac
}

# launchd installer — resolves every machine-specific value (node/npm binary
# paths, $HOME, install dir) at install time and rewrites the @@MARKER@@
# placeholders in launchd/*.plist.template before loading them.
#
# Why templates instead of committed plists with sed-rewrite:
#   1. /opt/homebrew/bin/node is not portable. Apple-Silicon Homebrew users
#      have it; Intel Homebrew (/usr/local), the official Node .pkg installer
#      (/usr/local/bin/node), nvm (~/.nvm/...), Volta, fnm, and asdf all do
#      not. launchd execs ProgramArguments[0] directly — no shell, no PATH
#      lookup — so the wrong path fails silently and the watchdog thrashes.
#   2. The previous implementation only rewrote the WorkingDirectory string,
#      so any other absolute path (notably HOME=... inside the watchdog's
#      inline script) leaked the original author's username into the deployed
#      plist. Templating with explicit markers makes the contract obvious.
install_launchd_agents() {
    local la_dir="$HOME/Library/LaunchAgents"
    mkdir -p "$la_dir"

    # Backend plist runs dist/index.js, so ensure it's built.
    if [ ! -f "$SCRIPT_DIR/dist/index.js" ]; then
        print_step "Building backend (required for launchd)..."
        (cd "$SCRIPT_DIR" && npm run build)
    fi

    local node_bin npm_bin
    node_bin=$(command -v node 2>/dev/null || true)
    npm_bin=$(command -v npm 2>/dev/null || true)

    if [ -z "$node_bin" ] || [ ! -x "$node_bin" ]; then
        print_error "Could not resolve 'node' on PATH — install Node.js and re-run."
        return 1
    fi
    if [ -z "$npm_bin" ] || [ ! -x "$npm_bin" ]; then
        print_error "Could not resolve 'npm' on PATH — install npm and re-run."
        return 1
    fi
    case "$node_bin" in /*) ;; *) print_error "Resolved node path is not absolute: $node_bin"; return 1 ;; esac
    case "$npm_bin"  in /*) ;; *) print_error "Resolved npm path is not absolute: $npm_bin"; return 1 ;; esac

    local node_dir npm_dir launchd_path
    node_dir=$(dirname "$node_bin")
    npm_dir=$(dirname "$npm_bin")
    if [ "$node_dir" = "$npm_dir" ]; then
        launchd_path="$node_dir:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
    else
        launchd_path="$node_dir:$npm_dir:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
    fi

    print_info "Resolved node: $node_bin"
    print_info "Resolved npm:  $npm_bin"

    local installed=0 failed=0
    shopt -s nullglob
    local templates=("$SCRIPT_DIR"/launchd/com.priority-forge.*.plist.template)
    shopt -u nullglob
    if [ ${#templates[@]} -eq 0 ]; then
        print_error "No launchd templates found in $SCRIPT_DIR/launchd/"
        return 1
    fi

    for tpl in "${templates[@]}"; do
        local base name target
        base=$(basename "$tpl")
        name="${base%.template}"
        target="$la_dir/$name"

        sed \
            -e "s|@@NODE@@|$node_bin|g" \
            -e "s|@@NPM@@|$npm_bin|g" \
            -e "s|@@INSTALL_DIR@@|$SCRIPT_DIR|g" \
            -e "s|@@HOME@@|$HOME|g" \
            -e "s|@@PATH@@|$launchd_path|g" \
            "$tpl" > "$target"

        # Refuse to load anything that still has a marker — that would mean a
        # new template variable shipped without an installer update, and
        # launchd would silently exec the literal "@@FOO@@" string.
        if grep -q '@@[A-Z_]\{1,\}@@' "$target"; then
            print_error "Unrewritten @@MARKER@@ left in $target — refusing to load:"
            grep -n '@@[A-Z_]\{1,\}@@' "$target" || true
            failed=$((failed + 1))
            continue
        fi

        launchctl unload "$target" 2>/dev/null || true
        if launchctl load "$target"; then
            print_success "Loaded $name"
            installed=$((installed + 1))
        else
            print_error "Failed to load $name (check ~/Library/LaunchAgents/$name)"
            failed=$((failed + 1))
        fi
    done

    if [ "$installed" -eq 0 ] || [ "$failed" -gt 0 ]; then
        return 1
    fi
    return 0
}

# Start backend server
start_backend() {
    ensure_pid_dir
    cd "$SCRIPT_DIR"

    if is_running "$PID_DIR/backend.pid"; then
        print_warning "Backend is already running (PID: $(get_pid "$PID_DIR/backend.pid"))"
        return 0
    fi

    print_step "Starting backend server..."
    
    # Start backend in background
    nohup npm run dev > "$PID_DIR/backend.log" 2>&1 &
    local pid=$!
    echo $pid > "$PID_DIR/backend.pid"
    
    # Wait a moment for the server to start
    sleep 2
    
    if is_running "$PID_DIR/backend.pid"; then
        print_success "Backend started on http://localhost:3456 (PID: $pid)"
        return 0
    else
        print_error "Backend failed to start. Check logs: $PID_DIR/backend.log"
        rm -f "$PID_DIR/backend.pid"
        return 1
    fi
}

# Start frontend server
start_frontend() {
    ensure_pid_dir
    cd "$SCRIPT_DIR/frontend"

    if is_running "$PID_DIR/frontend.pid"; then
        print_warning "Frontend is already running (PID: $(get_pid "$PID_DIR/frontend.pid"))"
        return 0
    fi

    print_step "Starting frontend dev server..."
    
    # Start frontend in background
    nohup npm run dev > "$PID_DIR/frontend.log" 2>&1 &
    local pid=$!
    echo $pid > "$PID_DIR/frontend.pid"
    
    # Wait a moment for the server to start. Vite usually prints its "Local:"
    # banner within ~1s, but on a cold install it can take 5-7s while
    # dependency optimization runs, so we poll up to 8s before giving up and
    # falling back to the hardcoded default in detect_frontend_port.
    sleep 3

    if is_running "$PID_DIR/frontend.pid"; then
        local port
        local waited=0
        while [ $waited -lt 5 ]; do
            port=$(detect_frontend_port "$pid" "$PID_DIR/frontend.log")
            # If we got a port from process inspection (not just the 5173
            # fallback), or we found Vite's banner in the log, stop waiting.
            if [ "$port" != "5173" ] || grep -q 'Local:' "$PID_DIR/frontend.log" 2>/dev/null; then
                break
            fi
            sleep 1
            waited=$((waited + 1))
        done
        print_success "Frontend started on http://localhost:$port (PID: $pid)"
        return 0
    else
        print_error "Frontend failed to start. Check logs: $PID_DIR/frontend.log"
        rm -f "$PID_DIR/frontend.pid"
        return 1
    fi
}

# Start full stack
do_start() {
    check_prerequisites
    
    echo ""
    echo -e "${BOLD}Starting Priority Forge Full Stack${NC}"
    echo ""
    
    start_backend
    start_frontend

    local frontend_pid frontend_port
    frontend_pid=$(get_pid "$PID_DIR/frontend.pid")
    frontend_port=$(detect_frontend_port "$frontend_pid" "$PID_DIR/frontend.log")

    echo ""
    echo -e "${GREEN}╔════════════════════════════════════════╗${NC}"
    echo -e "${GREEN}║       Servers Running!                 ║${NC}"
    echo -e "${GREEN}╚════════════════════════════════════════╝${NC}"
    echo ""
    echo "  Backend:  http://localhost:3456"
    echo "  Frontend: http://localhost:$frontend_port"
    echo ""
    echo "Run './setup.sh stop' to stop all servers"
    echo "Run './setup.sh logs' to view server logs"
    echo ""
}

# Start only backend
do_start_backend() {
    check_prerequisites
    
    echo ""
    echo -e "${BOLD}Starting Priority Forge Backend${NC}"
    echo ""
    
    start_backend
    
    echo ""
    echo "Run './setup.sh stop' to stop the server"
    echo ""
}

# Start only frontend
do_start_frontend() {
    check_prerequisites
    
    echo ""
    echo -e "${BOLD}Starting Priority Forge Frontend${NC}"
    echo ""
    
    # Check if backend is running
    if ! is_running "$PID_DIR/backend.pid"; then
        print_warning "Backend is not running. Frontend may not work correctly."
        echo "Start backend with: ./setup.sh start:backend"
        echo ""
    fi
    
    start_frontend
    
    echo ""
    echo "Run './setup.sh stop' to stop the server"
    echo ""
}

# Stop all servers
do_stop() {
    ensure_pid_dir
    
    echo ""
    echo -e "${BOLD}Stopping Priority Forge Servers${NC}"
    echo ""
    
    local stopped=false
    
    # Stop backend
    if is_running "$PID_DIR/backend.pid"; then
        local pid=$(get_pid "$PID_DIR/backend.pid")
        print_step "Stopping backend (PID: $pid)..."
        kill "$pid" 2>/dev/null || true
        rm -f "$PID_DIR/backend.pid"
        print_success "Backend stopped"
        stopped=true
    fi
    
    # Stop frontend
    if is_running "$PID_DIR/frontend.pid"; then
        local pid=$(get_pid "$PID_DIR/frontend.pid")
        print_step "Stopping frontend (PID: $pid)..."
        kill "$pid" 2>/dev/null || true
        rm -f "$PID_DIR/frontend.pid"
        print_success "Frontend stopped"
        stopped=true
    fi
    
    # Also try to kill any orphaned processes on the default ports
    if lsof -ti :3456 > /dev/null 2>&1; then
        print_step "Killing process on port 3456..."
        kill $(lsof -ti :3456) 2>/dev/null || true
        stopped=true
    fi
    
    # Sweep the Vite default and the first few auto-incremented ports — a
    # frontend that landed on 5174 because 5173 was busy still belongs to us.
    for p in 5173 5174 5175 5176 5177 5178; do
        if lsof -ti :$p > /dev/null 2>&1; then
            print_step "Killing process on port $p..."
            kill $(lsof -ti :$p) 2>/dev/null || true
            stopped=true
        fi
    done
    
    if [ "$stopped" = false ]; then
        print_info "No servers were running"
    else
        echo ""
        print_success "All servers stopped"
    fi
    echo ""
}

# Check server status
do_status() {
    ensure_pid_dir
    
    echo ""
    echo -e "${BOLD}Priority Forge Server Status${NC}"
    echo ""
    
    # Check backend
    if is_running "$PID_DIR/backend.pid"; then
        local pid=$(get_pid "$PID_DIR/backend.pid")
        echo -e "  Backend:  ${GREEN}● Running${NC} (PID: $pid) - http://localhost:3456"
    elif lsof -ti :3456 > /dev/null 2>&1; then
        local pid=$(lsof -ti :3456 | head -1)
        echo -e "  Backend:  ${YELLOW}● Running (unmanaged)${NC} (PID: $pid) - http://localhost:3456"
    else
        echo -e "  Backend:  ${RED}○ Stopped${NC}"
    fi
    
    # Check frontend. Vite may have auto-incremented past 5173, so resolve the
    # port from the process itself rather than trusting the default.
    if is_running "$PID_DIR/frontend.pid"; then
        local pid=$(get_pid "$PID_DIR/frontend.pid")
        local port=$(detect_frontend_port "$pid" "$PID_DIR/frontend.log")
        echo -e "  Frontend: ${GREEN}● Running${NC} (PID: $pid) - http://localhost:$port"
    elif lsof -ti :5173 > /dev/null 2>&1; then
        local pid=$(lsof -ti :5173 | head -1)
        echo -e "  Frontend: ${YELLOW}● Running (unmanaged)${NC} (PID: $pid) - http://localhost:5173"
    else
        # Scan a small range for an unmanaged Vite that auto-incremented.
        local found_port=""
        local found_pid=""
        for p in 5174 5175 5176 5177 5178; do
            if lsof -ti :$p > /dev/null 2>&1; then
                found_pid=$(lsof -ti :$p | head -1)
                found_port=$p
                break
            fi
        done
        if [ -n "$found_port" ]; then
            echo -e "  Frontend: ${YELLOW}● Running (unmanaged)${NC} (PID: $found_pid) - http://localhost:$found_port"
        else
            echo -e "  Frontend: ${RED}○ Stopped${NC}"
        fi
    fi
    
    echo ""
}

# Show logs
do_logs() {
    ensure_pid_dir
    
    echo ""
    echo -e "${BOLD}Priority Forge Server Logs${NC}"
    echo ""
    
    if [ -f "$PID_DIR/backend.log" ]; then
        echo -e "${CYAN}=== Backend Logs (last 20 lines) ===${NC}"
        tail -20 "$PID_DIR/backend.log"
        echo ""
    else
        echo "No backend logs found"
    fi
    
    if [ -f "$PID_DIR/frontend.log" ]; then
        echo -e "${CYAN}=== Frontend Logs (last 20 lines) ===${NC}"
        tail -20 "$PID_DIR/frontend.log"
        echo ""
    else
        echo "No frontend logs found"
    fi
    
    echo ""
    echo "For live logs, run:"
    echo "  tail -f $PID_DIR/backend.log"
    echo "  tail -f $PID_DIR/frontend.log"
    echo ""
}

# Run verification
do_verify() {
    cd "$SCRIPT_DIR"
    echo ""
    print_step "Verifying setup..."
    npx tsx scripts/verify.ts
    echo ""
}

# Copy cursor-plugin/ into ~/.cursor/plugins/priority-forge/ so a user-global
# Cursor plugin install is reproducible across machines. Idempotent: deletes
# the target directory first so stale files from previous versions don't survive.
# Also refreshes the repo-path discovery file so the plugin's sessionStart hook
# can locate setup.sh regardless of where Cursor is launched from.
install_cursor_plugin() {
    local src="$SCRIPT_DIR/cursor-plugin"
    local dest="$HOME/.cursor/plugins/priority-forge"

    if [ ! -d "$src" ]; then
        print_error "cursor-plugin/ not found at $src — are you in the Priority Forge repo?"
        exit 1
    fi

    print_step "Installing Cursor plugin bundle to $dest..."
    mkdir -p "$(dirname "$dest")"
    rm -rf "$dest"
    cp -R "$src" "$dest"
    chmod +x "$dest/hooks/sessionStart.sh" 2>/dev/null || true
    print_success "Plugin files copied"

    # Refresh the repo-path discovery file so the plugin's hook can locate setup.sh.
    local discovery_dir
    if [ "$(detect_os)" = "macos" ]; then
        discovery_dir="$HOME/Library/Application Support/priority-forge"
    else
        discovery_dir="$HOME/.local/share/priority-forge"
    fi
    mkdir -p "$discovery_dir"
    echo "$SCRIPT_DIR" > "$discovery_dir/repo-path"
    print_success "Repo-path discovery file: $discovery_dir/repo-path"

    echo ""
    echo -e "${BOLD}Next steps:${NC}"
    echo "  1. Open Cursor's plugin settings and enable 'priority-forge'."
    echo "  2. Ensure the backend is running:  ./setup.sh status"
    echo "     (or install persistent services: ./setup.sh install-systemd / install-launchd)"
    echo "  3. Restart Cursor completely."
    echo ""
    print_info "The plugin bundles its own rule, MCP entry, and sessionStart hook."
    print_info "It does not install the backend service — that's still './setup.sh install'."
    echo ""
}

# Parse optional flags before routing to a subcommand. Supported flags must
# appear either before or after the positional command (e.g. --no-service).
POSITIONAL_ARGS=()
NO_SERVICE=0
for arg in "$@"; do
    case "$arg" in
        --no-service)
            NO_SERVICE=1
            ;;
        --help|-h)
            POSITIONAL_ARGS+=("help")
            ;;
        *)
            POSITIONAL_ARGS+=("$arg")
            ;;
    esac
done
export NO_SERVICE
set -- "${POSITIONAL_ARGS[@]:-install}"

# Main command router
case "${1:-install}" in
    install|"")
        do_install
        ;;
    start)
        do_start
        ;;
    start:backend)
        do_start_backend
        ;;
    start:frontend)
        do_start_frontend
        ;;
    stop)
        do_stop
        ;;
    status)
        do_status
        ;;
    logs)
        do_logs
        ;;
    verify)
        do_verify
        ;;
    install-systemd)
        bash "$SCRIPT_DIR/scripts/install-systemd.sh" install
        ;;
    install-launchd)
        install_launchd_agents
        ;;
    install-cursor-plugin)
        install_cursor_plugin
        ;;
    help|--help|-h)
        show_help
        ;;
    *)
        print_error "Unknown command: $1"
        echo ""
        show_help
        exit 1
        ;;
esac
