set shell := ["bash", "-eo", "pipefail", "-c"]

default_destination := "platform=iOS Simulator,name=iPhone 17 Pro"
default_destination_b := "platform=iOS Simulator,name=iPhone Air"
default_destination_c := "platform=iOS Simulator,name=iPhone 17 Pro Max"
compose_file := "integration/docker-compose.yml"
base_url_1 := "http://localhost:13001"
base_url_2 := "http://localhost:13002"
base_url_3 := "http://localhost:13003"
readonly_classes := "LoginUITests CommitHistoryUITests PaginationUITests RepositoryUITests IssueUITests PullRequestUITests OverviewCreateUITests HomeScreenUITests PermissionUITests MergedInstanceReadOnlyUITests ActionsUITests"
mutating_a_classes := "MergedInstanceUITests IssueMutatingUITests NotificationsUITests OverviewCreateMutatingUITests"
mutating_b_classes := "PullRequestMutatingUITests RepositoryMutatingUITests"

# List all available tasks
default:
    @just --list

# Build the app
build destination=default_destination:
    xcodebuild -project Forji/Forji.xcodeproj -scheme Forji -destination '{{destination}}' build 2>&1 | xcbeautify

# Run app unit tests
test destination=default_destination:
    xcodebuild -project Forji/Forji.xcodeproj -scheme Forji -destination '{{destination}}' test -only-testing:ForjiTests 2>&1 | xcbeautify

# Lint Swift code
lint:
    swiftlint lint Forji/Forji

# Format Swift code
format:
    swiftformat Forji/Forji

pbxproj := "Forji/Forji.xcodeproj/project.pbxproj"

# Show current app version
version:
    @grep -m1 'MARKETING_VERSION' {{pbxproj}} | sed 's/.*= *//;s/;.*//'

changelog := "CHANGELOG.md"

# Preview the changelog entries for unreleased commits (since the latest tag)
changelog:
    @git cliff --config cliff.toml --unreleased --strip header

# Release a new version: bump version, generate the changelog, tag, commit, and push
release new_version:
    #!/usr/bin/env bash
    set -eo pipefail
    NEW="{{new_version}}"
    TAG="v$NEW"

    # Refuse to release from a dirty tree so the release commit stays clean.
    if [ -n "$(git status --porcelain)" ]; then
      echo "Working tree is not clean. Commit or stash your changes before releasing."
      exit 1
    fi

    # Refuse to clobber an existing tag.
    if git rev-parse "$TAG" >/dev/null 2>&1; then
      echo "Tag $TAG already exists."
      exit 1
    fi

    PREV=$(grep -m1 'MARKETING_VERSION' {{pbxproj}} | sed 's/.*= *//;s/;.*//')
    echo "Releasing $PREV -> $NEW"

    # 1. Bump the version in the Xcode project.
    sed -i '' "s/MARKETING_VERSION = [^;]*/MARKETING_VERSION = $NEW/" {{pbxproj}}
    sed -i '' "s/CURRENT_PROJECT_VERSION = [^;]*/CURRENT_PROJECT_VERSION = $NEW/" {{pbxproj}}

    # 2. Generate the changelog section for the new version from the conventional
    #    commits since the last tag, prepending it under the header.
    git cliff --config cliff.toml --unreleased --tag "$TAG" --prepend {{changelog}}

    # 3. Commit, tag, and push.
    git add {{pbxproj}} {{changelog}}
    git commit -m "chore: release $NEW"
    git tag -a "$TAG" -m "Release $NEW"
    git push origin HEAD
    git push origin "$TAG"
    echo "Released $NEW and pushed $TAG."

# Clean build artifacts
clean:
    xcodebuild -project Forji/Forji.xcodeproj -scheme Forji clean 2>&1 | xcbeautify

# Create simulators for justfile destinations on the latest installed iOS runtime
sim-update:
    #!/usr/bin/env bash
    set -eo pipefail
    DEVICES=("iPhone 17 Pro" "iPhone Air" "iPhone 17 Pro Max")
    LATEST=$(xcrun simctl list runtimes -j | jq '[.runtimes[] | select(.platform == "iOS" and .isAvailable)] | sort_by(.version | split(".") | map(tonumber)) | last')
    if [ -z "$LATEST" ] || [ "$LATEST" = "null" ]; then
      echo "No iOS simulator runtime found. Install one in Xcode → Settings → Platforms."
      exit 1
    fi
    LATEST_VERSION=$(echo "$LATEST" | jq -r '.version')
    LATEST_IDENTIFIER=$(echo "$LATEST" | jq -r '.identifier')
    echo "Latest iOS runtime: $LATEST_VERSION ($LATEST_IDENTIFIER)"
    EXISTING=$(xcodebuild -project Forji/Forji.xcodeproj -scheme Forji -showdestinations 2>&1 || true)
    CREATED=0
    for DEVICE in "${DEVICES[@]}"; do
      if echo "$EXISTING" | grep -E "OS:${LATEST_VERSION//./\\.},.*name:${DEVICE} \}" >/dev/null; then
        echo "  ✓ $DEVICE already exists on $LATEST_VERSION"
      else
        DEVICE_TYPE_ID=$(xcrun simctl list devicetypes -j | jq -r --arg n "$DEVICE" '.devicetypes[] | select(.name == $n) | .identifier')
        if [ -z "$DEVICE_TYPE_ID" ]; then
          echo "  ✗ Device type not found: $DEVICE (skipping)"
          continue
        fi
        echo "  → Creating $DEVICE on $LATEST_VERSION..."
        xcrun simctl create "$DEVICE" "$DEVICE_TYPE_ID" "$LATEST_IDENTIFIER" >/dev/null
        CREATED=$((CREATED + 1))
      fi
    done
    if [ "$CREATED" -gt 0 ]; then
      echo "Created $CREATED simulator(s)."
    else
      echo "All simulators are up to date."
    fi

# Build, install, and launch in simulator
run destination=default_destination:
    #!/usr/bin/env bash
    set -eo pipefail
    SIM_NAME="$(echo '{{destination}}' | sed -n 's/.*name=\([^,]*\).*/\1/p')"
    xcrun simctl boot "$SIM_NAME" 2>/dev/null || true
    open -a Simulator
    xcodebuild -project Forji/Forji.xcodeproj -scheme Forji -destination '{{destination}}' build 2>&1 | xcbeautify
    BUILT_APP="$(xcodebuild -project Forji/Forji.xcodeproj -scheme Forji -destination '{{destination}}' -showBuildSettings 2>/dev/null | grep ' BUILT_PRODUCTS_DIR' | awk '{print $3}')/Forji.app"
    xcrun simctl install "$SIM_NAME" "$BUILT_APP"
    xcrun simctl launch "$SIM_NAME" "$(defaults read "$BUILT_APP/Info.plist" CFBundleIdentifier)"

# List all UI integration tests
test-list:
    @grep -rh 'func test.*()' Forji/ForjiUITests/*.swift \
        | grep -v 'override\|private\|ForgejoUITestBase' \
        | sed 's/.*func //' | sed 's/().*//' \
        | while read -r method; do \
            file=$(grep -rl "func $method()" Forji/ForjiUITests/*.swift | head -1); \
            class=$(grep 'class.*:' "$file" | head -1 | sed 's/.*class //' | sed 's/[: ].*//' ); \
            printf "  %s/%s\n" "$class" "$method"; \
        done | sort

# Start Forgejo Docker containers
docker-up:
    docker compose -f {{compose_file}} up -d --wait --wait-timeout 120

# Stop Forgejo Docker containers and clean up
docker-down:
    docker compose -f {{compose_file}} down -v 2>/dev/null || true
    rm -f /tmp/forgejo_test_url.txt /tmp/forgejo_test_url_*.txt
    rm -f /tmp/forgejo_test_url2.txt /tmp/forgejo_test_url2_*.txt
    rm -f /tmp/forgejo_test_token.txt /tmp/forgejo_test_token_*.txt

# Clear integration test caches (seed snapshots, temp files)
clean-integration:
    rm -f integration/.forgejo-seed-snapshot.tar.gz integration/.forgejo-seed-hash
    rm -f /tmp/forgejo_test_url.txt /tmp/forgejo_test_url_*.txt
    rm -f /tmp/forgejo_test_url2.txt /tmp/forgejo_test_url2_*.txt
    rm -f /tmp/forgejo_test_token.txt /tmp/forgejo_test_token_*.txt
    rm -rf integration/forgejo-seed/.build
    @echo "Integration test caches cleared."

# Seed test data into Forgejo instances (with snapshot caching)
seed:
    #!/usr/bin/env bash
    set -eo pipefail
    COMPOSE_FILE="{{compose_file}}"
    BASE_URLS=("{{base_url_1}}" "{{base_url_2}}" "{{base_url_3}}")
    SERVICES=(forgejo-1 forgejo-2 forgejo-3)
    SNAPSHOT_FILE="integration/.forgejo-seed-snapshot.tar.gz"
    HASH_FILE="integration/.forgejo-seed-hash"
    SETUP_HASH=$(find integration/forgejo-seed/Sources -name '*.swift' -print0 | sort -z | xargs -0 shasum -a 256 | shasum -a 256 | awk '{print $1}')
    wait_for_instance() {
      local url="$1" elapsed=0
      until curl -sf "$url/api/v1/version" > /dev/null 2>&1; do
        if [ "$elapsed" -ge 60 ]; then
          echo "Timed out waiting for $url after 60s"
          exit 1
        fi
        sleep 1
        elapsed=$((elapsed + 1))
      done
      echo "  $url is ready (${elapsed}s)"
    }
    if [ -f "$SNAPSHOT_FILE" ] && [ -f "$HASH_FILE" ] && [ "$(cat "$HASH_FILE")" = "$SETUP_HASH" ]; then
      echo "Restoring from seed snapshot..."
      for svc in "${SERVICES[@]}"; do
        CID=$(docker compose -f "$COMPOSE_FILE" ps -q "$svc")
        docker cp "$SNAPSHOT_FILE" "$CID:/tmp/snapshot.tar.gz" &
      done
      wait
      for svc in "${SERVICES[@]}"; do
        CID=$(docker compose -f "$COMPOSE_FILE" ps -q "$svc")
        docker exec "$CID" sh -c "cd / && tar xzf /tmp/snapshot.tar.gz" &
      done
      wait
      docker compose -f "$COMPOSE_FILE" restart "${SERVICES[@]}"
      for url in "${BASE_URLS[@]}"; do
        wait_for_instance "$url" &
      done
      wait
      echo "Snapshot restored to all instances."
    else
      echo "Seeding test data..."
      swift run --package-path integration/forgejo-seed forgejo-seed "${BASE_URLS[0]}" forgejo-1
      echo "Snapshotting seed data..."
      CID_1=$(docker compose -f "$COMPOSE_FILE" ps -q forgejo-1)
      docker exec "$CID_1" sh -c "cd / && tar czf /tmp/snapshot.tar.gz data"
      docker cp "$CID_1:/tmp/snapshot.tar.gz" "$SNAPSHOT_FILE"
      echo "$SETUP_HASH" > "$HASH_FILE"
      echo "Restoring snapshot to other instances..."
      for svc in "${SERVICES[@]:1}"; do
        CID=$(docker compose -f "$COMPOSE_FILE" ps -q "$svc")
        docker cp "$SNAPSHOT_FILE" "$CID:/tmp/snapshot.tar.gz"
        docker exec "$CID" sh -c "cd / && tar xzf /tmp/snapshot.tar.gz" &
      done
      wait
      docker compose -f "$COMPOSE_FILE" restart "${SERVICES[@]:1}"
      for url in "${BASE_URLS[@]:1}"; do
        wait_for_instance "$url" &
      done
      wait
      echo "All instances seeded."
    fi

# Run read-only UI tests (assumes build + seed + URL files are set up)
test-readonly destination=default_destination:
    #!/usr/bin/env bash
    set -eo pipefail
    ARGS=""
    for cls in {{readonly_classes}}; do
      ARGS="$ARGS -only-testing:ForjiUITests/$cls"
    done
    xcodebuild test-without-building \
      -project Forji/Forji.xcodeproj \
      -scheme Forji \
      -destination '{{destination}}' \
      -parallel-testing-enabled YES \
      $ARGS 2>&1 | xcbeautify

# Run mutating group A: MergedInstanceUITests (needs 2 instances)
test-mutating-a destination=default_destination_b:
    #!/usr/bin/env bash
    set -eo pipefail
    ARGS=""
    for cls in {{mutating_a_classes}}; do
      ARGS="$ARGS -only-testing:ForjiUITests/$cls"
    done
    xcodebuild test-without-building \
      -project Forji/Forji.xcodeproj \
      -scheme Forji \
      -destination '{{destination}}' \
      -parallel-testing-enabled NO \
      $ARGS 2>&1 | xcbeautify

# Run mutating group B: all non-merged mutating tests
test-mutating-b destination=default_destination_c:
    #!/usr/bin/env bash
    set -eo pipefail
    ARGS=""
    for cls in {{mutating_b_classes}}; do
      ARGS="$ARGS -only-testing:ForjiUITests/$cls"
    done
    xcodebuild test-without-building \
      -project Forji/Forji.xcodeproj \
      -scheme Forji \
      -destination '{{destination}}' \
      -parallel-testing-enabled NO \
      $ARGS 2>&1 | xcbeautify

# Run full UI integration test suite (requires Docker)
test-ui destination_a=default_destination destination_b=default_destination_b destination_c=default_destination_c:
    #!/usr/bin/env bash
    set -eo pipefail
    cleanup() {
      just docker-down
    }
    trap cleanup EXIT
    just sim-boot '{{destination_a}}' '{{destination_b}}' '{{destination_c}}' &
    just docker-up &
    just build-for-testing '{{destination_a}}' '{{destination_b}}' '{{destination_c}}' &
    wait
    just seed
    just _write-url-files '{{destination_a}}' '{{destination_b}}' '{{destination_c}}'
    LOG_RO="/tmp/forji_test_readonly.log"
    LOG_MA="/tmp/forji_test_mutating_a.log"
    LOG_MB="/tmp/forji_test_mutating_b.log"
    EXIT_RO=0
    EXIT_MA=0
    EXIT_MB=0
    echo "Starting read-only tests on {{destination_a}}..."
    just test-readonly '{{destination_a}}' > "$LOG_RO" 2>&1 &
    PID_RO=$!
    echo "Starting mutating-a (merged) tests on {{destination_b}}..."
    just test-mutating-a '{{destination_b}}' > "$LOG_MA" 2>&1 &
    PID_MA=$!
    echo "Starting mutating-b (rest) tests on {{destination_c}}..."
    just test-mutating-b '{{destination_c}}' > "$LOG_MB" 2>&1 &
    PID_MB=$!
    wait $PID_RO || EXIT_RO=$?
    wait $PID_MA || EXIT_MA=$?
    wait $PID_MB || EXIT_MB=$?
    echo "=== Read-only tests output ==="
    cat "$LOG_RO"
    echo ""
    echo "=== Mutating-A (merged) tests output ==="
    cat "$LOG_MA"
    echo ""
    echo "=== Mutating-B (rest) tests output ==="
    cat "$LOG_MB"
    rm -f "$LOG_RO" "$LOG_MA" "$LOG_MB"
    FAILED=0
    if [ "$EXIT_RO" -ne 0 ]; then echo "Read-only tests failed (exit $EXIT_RO)."; FAILED=1; fi
    if [ "$EXIT_MA" -ne 0 ]; then echo "Mutating-A tests failed (exit $EXIT_MA)."; FAILED=1; fi
    if [ "$EXIT_MB" -ne 0 ]; then echo "Mutating-B tests failed (exit $EXIT_MB)."; FAILED=1; fi
    if [ "$FAILED" -ne 0 ]; then
      echo "Integration tests FAILED."
      exit 1
    fi
    echo ""
    echo "All integration tests passed."

# Run a single UI test (requires Docker). Use `just test-list` to see available tests.
test-one filter="" destination=default_destination:
    #!/usr/bin/env bash
    set -eo pipefail
    FILTER="{{filter}}"
    if [ -z "$FILTER" ]; then
      echo "Usage: just test-one <Class/method>"
      echo ""
      echo "Run 'just test-list' to see available tests."
      exit 1
    fi
    cleanup() {
      just docker-down
    }
    trap cleanup EXIT
    just sim-boot '{{destination}}' '{{destination}}' '{{destination}}' &
    just docker-up &
    just build-for-testing '{{destination}}' '{{destination}}' '{{destination}}' &
    wait
    just seed
    just _write-url-file '{{destination}}' '{{base_url_1}}' true
    just _write-url2-file '{{destination}}' '{{base_url_2}}' true
    xcodebuild test-without-building \
      -project Forji/Forji.xcodeproj \
      -scheme Forji \
      -destination '{{destination}}' \
      -parallel-testing-enabled NO \
      -only-testing:"ForjiUITests/$FILTER" 2>&1 | xcbeautify

[private]
sim-boot destination_a=default_destination destination_b=default_destination_b destination_c=default_destination_c:
    #!/usr/bin/env bash
    set -eo pipefail
    for dest in '{{destination_a}}' '{{destination_b}}' '{{destination_c}}'; do
      SIM="$(echo "$dest" | sed -n 's/.*name=\([^,]*\).*/\1/p')"
      xcrun simctl boot "$SIM" 2>/dev/null || true
    done

[private]
build-for-testing destination_a=default_destination destination_b=default_destination_b destination_c=default_destination_c:
    xcodebuild build-for-testing \
      -project Forji/Forji.xcodeproj \
      -scheme Forji \
      -destination '{{destination_a}}' \
      -destination '{{destination_b}}' \
      -destination '{{destination_c}}' 2>&1 | xcbeautify

[private]
_write-url-file destination url fallback="false":
    #!/usr/bin/env bash
    set -eo pipefail
    SIM="$(echo '{{destination}}' | sed -n 's/.*name=\([^,]*\).*/\1/p')"
    SIM_SAFE="${SIM// /_}"
    echo "{{url}}" > "/tmp/forgejo_test_url_${SIM_SAFE}.txt"
    if [ "{{fallback}}" = "true" ]; then
      echo "{{url}}" > /tmp/forgejo_test_url.txt
    fi

[private]
_write-url2-file destination url fallback="false":
    #!/usr/bin/env bash
    set -eo pipefail
    SIM="$(echo '{{destination}}' | sed -n 's/.*name=\([^,]*\).*/\1/p')"
    SIM_SAFE="${SIM// /_}"
    echo "{{url}}" > "/tmp/forgejo_test_url2_${SIM_SAFE}.txt"
    if [ "{{fallback}}" = "true" ]; then
      echo "{{url}}" > /tmp/forgejo_test_url2.txt
    fi

[private]
_write-url-files destination_a=default_destination destination_b=default_destination_b destination_c=default_destination_c:
    just _write-url-file '{{destination_a}}' '{{base_url_1}}' true
    just _write-url-file '{{destination_b}}' '{{base_url_2}}'
    just _write-url-file '{{destination_c}}' '{{base_url_3}}'
    just _write-url2-file '{{destination_a}}' '{{base_url_2}}' true
    just _write-url2-file '{{destination_b}}' '{{base_url_1}}'
