# Derive a stable default port from the working directory so the same worktree
# usually gets the same port. Set VITE_PORT to override it.
default_vite_port := `python3 -c "import hashlib,os; h=int(hashlib.sha256(os.getcwd().encode()).hexdigest(),16); print(10000 + h % 55000)"`

# Default recipe
default:
    @just --list

# ── Dev Environment ──────────────────────────────────────────

# Install dependencies, build workspace packages, and activate git hooks
setup:
    cd ../ && pnpm install
    cd ../sdk && pnpm build
    cargo build --manifest-path ../../Cargo.toml -p goose-cli --bin goose

# ── Build & Check ────────────────────────────────────────────

# Run all checks (lint, format, typecheck, file sizes)
check:
    pnpm check
    pnpm typecheck

# Format code
fmt:
    pnpm format
    cd src-tauri && cargo fmt

# Check formatting without modifying
fmt-check:
    pnpm exec biome format .
    cd src-tauri && cargo fmt --check

# Run clippy on Tauri backend
clippy:
    cd src-tauri && TAURI_CONFIG='{"bundle":{"externalBin":[]}}' cargo clippy -- -D warnings

# Build the frontend
build:
    pnpm build

# Check Tauri Rust formatting
tauri-fmt-check:
    cd src-tauri && cargo fmt --check

# Check Tauri Rust types
tauri-check:
    cd src-tauri && TAURI_CONFIG='{"bundle":{"externalBin":[]}}' cargo check

# Full CI gate
ci: check clippy test build tauri-check

bundle:
    pnpm tauri build

# ── Test ─────────────────────────────────────────────────────

# Run unit/component tests
test:
    pnpm test

# Run tests in watch mode
test-watch:
    pnpm test:watch

# Run tests with coverage
test-coverage:
    pnpm test:coverage

# Run E2E smoke tests (builds first)
test-e2e:
    pnpm test:e2e:smoke

# Run all E2E tests (builds first)
test-e2e-all:
    pnpm test:e2e

# ── Run ──────────────────────────────────────────────────────

# Start the desktop app in dev mode
dev: build-core
    #!/usr/bin/env bash
    set -euo pipefail

    DEFAULT_VITE_PORT={{ default_vite_port }}
    VITE_PORT="${VITE_PORT:-$DEFAULT_VITE_PORT}"
    export VITE_PORT
    # Enable perf logs in the child `goose serve` process by default.
    # Override with e.g. RUST_LOG=info just dev to disable.
    export RUST_LOG="${RUST_LOG:-perf=debug,info}"
    PROJECT_DIR=$(pwd)
    REPO_ROOT=$(cd ../.. && pwd)
    DISTRO_DIR="${PROJECT_DIR}/distro"
    if [[ -z "${GOOSE_DISTRO_DIR:-}" && -d "${DISTRO_DIR}" ]]; then
        export GOOSE_DISTRO_DIR="${DISTRO_DIR}"
    fi
    LOCAL_GOOSE_DEBUG="${REPO_ROOT}/target/debug/goose"
    LOCAL_GOOSE_RELEASE="${REPO_ROOT}/target/release/goose"
    if [[ -x "${LOCAL_GOOSE_DEBUG}" ]]; then
        export GOOSE_BIN="${LOCAL_GOOSE_DEBUG}"
    elif [[ -x "${LOCAL_GOOSE_RELEASE}" ]]; then
        export GOOSE_BIN="${LOCAL_GOOSE_RELEASE}"
    else
        unset GOOSE_BIN
    fi
    EXTRA_CONFIG_ARGS=(--config "{\"build\":{\"devUrl\":\"http://localhost:${VITE_PORT}\",\"beforeDevCommand\":{\"script\":\"exec ./scripts/start-dev-vite.sh ${VITE_PORT}\",\"cwd\":\"${PROJECT_DIR}\",\"wait\":false}}}")

    if [[ -n "${GOOSE_BIN:-}" ]]; then
        echo "Using local goose binary: ${GOOSE_BIN}"
    else
        echo "No local goose binary found under ${REPO_ROOT}/target; falling back to PATH"
    fi

    if [[ -n "${GOOSE_DISTRO_DIR:-}" ]]; then
        echo "Using distro dir: ${GOOSE_DISTRO_DIR}"
    fi

    # In worktrees, generate a labeled icon so you can tell instances apart
    if git rev-parse --is-inside-work-tree &>/dev/null; then
        GIT_DIR=$(git rev-parse --git-dir)
        if [[ "$GIT_DIR" == *".git/worktrees/"* ]]; then
            BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD)
            WORKTREE_LABEL="${BRANCH_NAME##*/}"

            ICON_DIR="$(pwd)/src-tauri/target/dev-icons"
            mkdir -p "$ICON_DIR"
            DEV_ICON="$ICON_DIR/icon.icns"

            if swift scripts/generate-dev-icon.swift src-tauri/icons/icon.icns "$DEV_ICON" "$WORKTREE_LABEL"; then
                echo "🌳 Worktree: ${WORKTREE_LABEL}"
                EXTRA_CONFIG_ARGS+=(--config "{\"bundle\":{\"icon\":[\"$DEV_ICON\"]}}")
            fi
        fi
    fi

    pnpm tauri dev --features app-test-driver --config src-tauri/tauri.dev.conf.json "${EXTRA_CONFIG_ARGS[@]}"

# Start the desktop app with dev config
dev-debug: build-core
    #!/usr/bin/env bash
    set -euo pipefail

    DEFAULT_VITE_PORT={{ default_vite_port }}
    VITE_PORT="${VITE_PORT:-$DEFAULT_VITE_PORT}"
    export VITE_PORT
    # Enable perf logs in the child `goose serve` process by default.
    # Override with e.g. RUST_LOG=info just dev-debug to disable.
    export RUST_LOG="${RUST_LOG:-perf=debug,info}"
    PROJECT_DIR=$(pwd)
    REPO_ROOT=$(cd ../.. && pwd)
    DISTRO_DIR="$(pwd)/distro"
    if [[ -z "${GOOSE_DISTRO_DIR:-}" && -d "${DISTRO_DIR}" ]]; then
        export GOOSE_DISTRO_DIR="${DISTRO_DIR}"
    fi
    LOCAL_GOOSE_DEBUG="${REPO_ROOT}/target/debug/goose"
    LOCAL_GOOSE_RELEASE="${REPO_ROOT}/target/release/goose"
    if [[ -x "${LOCAL_GOOSE_DEBUG}" ]]; then
        export GOOSE_BIN="${LOCAL_GOOSE_DEBUG}"
    elif [[ -x "${LOCAL_GOOSE_RELEASE}" ]]; then
        export GOOSE_BIN="${LOCAL_GOOSE_RELEASE}"
    else
        unset GOOSE_BIN
    fi
    EXTRA_CONFIG_ARGS=(--config "{\"build\":{\"devUrl\":\"http://localhost:${VITE_PORT}\",\"beforeDevCommand\":{\"script\":\"exec ./scripts/start-dev-vite.sh ${VITE_PORT}\",\"cwd\":\"${PROJECT_DIR}\",\"wait\":false}}}")

    if [[ -n "${GOOSE_BIN:-}" ]]; then
        echo "Using local goose binary: ${GOOSE_BIN}"
    else
        echo "No local goose binary found under ${REPO_ROOT}/target; falling back to PATH"
    fi

    if [[ -n "${GOOSE_DISTRO_DIR:-}" ]]; then
        echo "Using distro dir: ${GOOSE_DISTRO_DIR}"
    fi

    # In worktrees, generate a labeled icon so you can tell instances apart
    if git rev-parse --is-inside-work-tree &>/dev/null; then
        GIT_DIR=$(git rev-parse --git-dir)
        if [[ "$GIT_DIR" == *".git/worktrees/"* ]]; then
            BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD)
            WORKTREE_LABEL="${BRANCH_NAME##*/}"

            ICON_DIR="$(pwd)/src-tauri/target/dev-icons"
            mkdir -p "$ICON_DIR"
            DEV_ICON="$ICON_DIR/icon.icns"

            if swift scripts/generate-dev-icon.swift src-tauri/icons/icon.icns "$DEV_ICON" "$WORKTREE_LABEL"; then
                echo "🌳 Worktree: ${WORKTREE_LABEL}"
                EXTRA_CONFIG_ARGS+=(--config "{\"bundle\":{\"icon\":[\"$DEV_ICON\"]}}")
            fi
        fi
    fi

    pnpm tauri dev --config src-tauri/tauri.dev.conf.json "${EXTRA_CONFIG_ARGS[@]}"

# Start only the frontend dev server
dev-frontend:
    pnpm dev

# Parse `git worktree list --porcelain` into tab-separated "path\tbranch" lines.
# Uses sub() instead of $2 so worktree paths containing spaces are preserved.
# Detached-HEAD worktrees (no branch line) are silently skipped.
_worktree_awk := '/^worktree / { sub(/^worktree /, ""); wt=$0 } /^branch refs\/heads\// { b=$2; sub(/^refs\/heads\//, "", b); print wt "\t" b }'

# Compute a stable vite port from a directory path passed as $1.
_port_cmd := "import hashlib,sys; h=int(hashlib.sha256(sys.argv[1].encode()).hexdigest(),16); print(10000+h%55000)"

# List worktrees with an active dev server
running:
    #!/usr/bin/env bash
    git worktree list --porcelain | awk '{{ _worktree_awk }}' \
    | while IFS=$'\t' read -r wt branch; do
        dir="$wt/ui/goose2"
        port=$(python3 -c '{{ _port_cmd }}' "$dir")
        if lsof -ti :"$port" &>/dev/null; then
            echo "$branch"
        fi
    done

# Kill the dev process (if running). Optionally pass a branch name to kill another worktree's process.
kill branch="":
    #!/usr/bin/env bash
    set -euo pipefail

    if [[ -n "{{ branch }}" ]]; then
        WORKTREE_PATH=$(git worktree list --porcelain \
            | awk '{{ _worktree_awk }}' \
            | awk -F'\t' -v branch="{{ branch }}" '$2 == branch { print $1; exit }')
        if [[ -z "$WORKTREE_PATH" ]]; then
            echo "No worktree found for branch '{{ branch }}'"
            exit 1
        fi
        TARGET_DIR="$WORKTREE_PATH/ui/goose2"
        VITE_PORT=$(python3 -c '{{ _port_cmd }}' "$TARGET_DIR")
    else
        DEFAULT_VITE_PORT={{ default_vite_port }}
        VITE_PORT="${VITE_PORT:-$DEFAULT_VITE_PORT}"
    fi

    PID=$(lsof -ti :"$VITE_PORT" 2>/dev/null | head -1) || true

    if [[ -z "$PID" ]]; then
        echo "No process found on port $VITE_PORT"
        exit 0
    fi

    PROC_NAME=$(ps -p "$PID" -o comm= 2>/dev/null) || true
    PROC_BASENAME="${PROC_NAME##*/}"
    if [[ "$PROC_BASENAME" != "node" ]]; then
        echo "Process on port $VITE_PORT is '$PROC_NAME', not 'node' — refusing to kill"
        exit 1
    fi

    PGID=$(ps -p "$PID" -o pgid= 2>/dev/null | tr -d ' ')
    if [[ -z "$PGID" || "$PGID" == "0" || "$PGID" == "1" ]]; then
        echo "Killing node (PID $PID) on port $VITE_PORT"
        kill -9 "$PID"
    else
        echo "Killing process group $PGID (found via node PID $PID on port $VITE_PORT)"
        kill -9 -"$PGID" 2>/dev/null || true
    fi

# Kill all dev processes across all worktrees
kill-all:
    #!/usr/bin/env bash
    set -euo pipefail
    branches=$(just running)
    if [[ -z "$branches" ]]; then
        echo "No running dev servers found"
        exit 0
    fi
    while read -r branch; do
        just kill "$branch" || true
    done <<< "$branches"

# ── Utilities ────────────────────────────────────────────────

# Clean build artifacts
clean:
    cd src-tauri && cargo clean
    rm -rf dist
    rm -rf node_modules

build-core:
    cargo build -p goose-cli --bin goose
