#!/bin/bash
# install-to-device.sh — build an iOS project fresh, install it on a connected
# iPhone/iPad, and force-relaunch it. No flags required for interactive use.
# Auto-detects the Xcode project/workspace, scheme, and device; .env or CLI
# flags can pin any of them. Run from anywhere inside the repo.
#
# AUTHOR. justin-murry
# LLM. claude-code
# LEARN. https://www.unparty.app/theunpartyunppp
# Version: 1.1.0
# Last Updated: 2026-09-25
set -euo pipefail

VERSION="1.1.0"

DEVICE_ID_ARG=""
PROJECT_ARG=""
SCHEME_ARG=""
while [ $# -gt 0 ]; do
  case "$1" in
    --device)
      DEVICE_ID_ARG="${2:-}"
      [ -n "$DEVICE_ID_ARG" ] || { echo "❌ --device requires a UDID argument" >&2; exit 1; }
      shift 2
      ;;
    --device=*)
      DEVICE_ID_ARG="${1#--device=}"
      shift
      ;;
    --project)
      PROJECT_ARG="${2:-}"
      [ -n "$PROJECT_ARG" ] || { echo "❌ --project requires a path argument" >&2; exit 1; }
      shift 2
      ;;
    --project=*)
      PROJECT_ARG="${1#--project=}"
      shift
      ;;
    --scheme)
      SCHEME_ARG="${2:-}"
      [ -n "$SCHEME_ARG" ] || { echo "❌ --scheme requires a name argument" >&2; exit 1; }
      shift 2
      ;;
    --scheme=*)
      SCHEME_ARG="${1#--scheme=}"
      shift
      ;;
    -h|--help)
      echo "install-to-device.sh v$VERSION"
      echo "Usage: $0 [--project <path>] [--scheme <name>] [--device <udid>]"
      echo ""
      echo "  --project <path>  Path to a .xcodeproj/.xcworkspace. Skips auto-detect."
      echo "  --scheme <name>   Scheme to build. Skips auto-detect."
      echo "  --device <udid>   Skip device auto-detect/picker and use this UDID directly."
      echo ""
      echo "  All three are also settable via .env (PROJECT, SCHEME, DEVICE_ID) —"
      echo "  see .env.example. With none set, everything is auto-detected: the"
      echo "  project/workspace and scheme from the current repo, and the device"
      echo "  from whatever's connected (prompting only when there's more than one)."
      echo ""
      echo "  Other env vars (or .env): CONFIGURATION, DERIVED_DATA_PATH, DEVELOPMENT_TEAM"
      exit 0
      ;;
    *)
      echo "❌ Unknown argument: $1 (see --help)" >&2
      exit 1
      ;;
  esac
done

CONFIGURATION="${CONFIGURATION:-Debug}"
DERIVED_DATA_PATH="${DERIVED_DATA_PATH:-.build/DeviceInstall}"

REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null)"
if [ -z "$REPO_ROOT" ]; then
  REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
fi
cd "$REPO_ROOT"

# shellcheck disable=SC1091
source "$REPO_ROOT/scripts/unparty_styles.sh"
show_unparty_header "Install to Device" "Build. Install. Relaunch."


trap 'echo "❌ Something went wrong. Scroll up for the step that failed."' ERR

# Personal overrides (project/scheme/device UDID, Apple Developer Team) live in
# .env, gitignored — see .env.example.
if [ -f "$REPO_ROOT/.env" ]; then
  set -a
  # shellcheck disable=SC1091
  source "$REPO_ROOT/.env"
  set +a
fi

# CLI flags win over .env.
if [ -n "$DEVICE_ID_ARG" ]; then
  DEVICE_ID="$DEVICE_ID_ARG"
fi
if [ -n "$PROJECT_ARG" ]; then
  PROJECT="$PROJECT_ARG"
fi
if [ -n "$SCHEME_ARG" ]; then
  SCHEME="$SCHEME_ARG"
fi

# --- Resolve project/workspace (flag > .env > auto-detect) ---
if [ -z "${PROJECT:-}" ]; then
  WORKSPACES=(*.xcworkspace)
  PROJECTS=(*.xcodeproj)
  [ -e "${WORKSPACES[0]}" ] || WORKSPACES=()
  [ -e "${PROJECTS[0]}" ] || PROJECTS=()

  if [ "${#WORKSPACES[@]}" -gt 0 ]; then
    CANDIDATES=("${WORKSPACES[@]}")
  elif [ "${#PROJECTS[@]}" -gt 0 ]; then
    CANDIDATES=("${PROJECTS[@]}")
  else
    if [ ! -t 0 ]; then
      echo "❌ No .xcworkspace or .xcodeproj found in $REPO_ROOT (non-interactive run)."
      echo "   Pass --project <path> or set PROJECT in .env."
      exit 1
    fi
    read -r -p "No .xcworkspace or .xcodeproj found here — enter the path to one: " PROJECT
    CANDIDATES=()
  fi

  if [ "${#CANDIDATES[@]}" -gt 1 ]; then
    echo "📋 Multiple projects found:"
    for i in "${!CANDIDATES[@]}"; do
      echo "   $((i+1))) ${CANDIDATES[$i]}"
    done
    if [ ! -t 0 ]; then
      echo "❌ Can't prompt for a choice (non-interactive run). Pass --project <path> or set PROJECT in .env."
      exit 1
    fi
    read -r -p "Pick one [1-${#CANDIDATES[@]}]: " choice
    PROJECT="${CANDIDATES[$((choice-1))]}"
  elif [ "${#CANDIDATES[@]}" -eq 1 ]; then
    PROJECT="${CANDIDATES[0]}"
  fi
fi

case "$PROJECT" in
  *.xcworkspace) PROJECT_FLAG="-workspace" ;;
  *) PROJECT_FLAG="-project" ;;
esac

# --- Resolve scheme (flag > .env > auto-detect) ---
if [ -z "${SCHEME:-}" ]; then
  SCHEMES=()
  while IFS= read -r line; do
    SCHEMES+=("$line")
  done < <(xcodebuild "$PROJECT_FLAG" "$PROJECT" -list 2>/dev/null | awk '/Schemes:/{f=1;next} /^$/{f=0} f{print $1}')

  if [ "${#SCHEMES[@]}" -eq 0 ]; then
    echo "❌ No schemes found in $PROJECT — make sure it has a shared scheme (Product → Scheme → Manage Schemes → check Shared)."
    exit 1
  elif [ "${#SCHEMES[@]}" -gt 1 ]; then
    echo "📋 Multiple schemes found:"
    for i in "${!SCHEMES[@]}"; do
      echo "   $((i+1))) ${SCHEMES[$i]}"
    done
    if [ ! -t 0 ]; then
      echo "❌ Can't prompt for a choice (non-interactive run). Pass --scheme <name> or set SCHEME in .env."
      exit 1
    fi
    read -r -p "Pick one [1-${#SCHEMES[@]}]: " choice
    SCHEME="${SCHEMES[$((choice-1))]}"
  else
    SCHEME="${SCHEMES[0]}"
  fi
fi

EXTRA_BUILD_ARGS=()
if [ -n "${DEVELOPMENT_TEAM:-}" ]; then
  EXTRA_BUILD_ARGS+=("DEVELOPMENT_TEAM=$DEVELOPMENT_TEAM")
fi

DEVICES_JSON="$(mktemp)"
INSTALL_JSON="$(mktemp)"
BUILD_LOG="$(mktemp)"
cleanup() { rm -f "$DEVICES_JSON" "$INSTALL_JSON" "$BUILD_LOG"; }
trap cleanup EXIT

# --- 1. Preflight ---
command -v xcrun >/dev/null || { echo "❌ xcrun not found — install Xcode Command Line Tools: xcode-select --install"; exit 1; }
command -v xcodebuild >/dev/null || { echo "❌ xcodebuild not found — install full Xcode from the App Store, not just the Command Line Tools"; exit 1; }

HAVE_JQ=1
command -v jq >/dev/null || HAVE_JQ=0
if [ "$HAVE_JQ" = 0 ]; then
  echo "⚠️  jq not found (brew install jq for more reliable device detection) — falling back to table parsing"
fi

echo "📋 install-to-device.sh v$VERSION — project: $PROJECT, scheme: $SCHEME"

# --- 2. Discover connected devices ---
if [ -n "${DEVICE_ID:-}" ]; then
  echo "📱 Using pinned device: $DEVICE_ID"
else
echo "🔎 Looking for connected devices..."
xcrun devicectl list devices --json-output "$DEVICES_JSON" >/dev/null

DEVICE_IDS=()
DEVICE_NAMES=()

if [ "$HAVE_JQ" = 1 ]; then
  while IFS=$'\t' read -r id name; do
    DEVICE_IDS+=("$id")
    DEVICE_NAMES+=("$name")
  done < <(jq -r '.result.devices[]
    | select(.connectionProperties.tunnelState == "connected")
    | select(.hardwareProperties.deviceType == "iPhone" or .hardwareProperties.deviceType == "iPad")
    | [.identifier, .deviceProperties.name] | @tsv' "$DEVICES_JSON")
else
  # Fallback: parse the human-readable table (Name / Hostname / Identifier / State / Model).
  # Only rows whose State column reads "connected" and whose Model looks like an iPhone/iPad are kept.
  while IFS= read -r line; do
    if echo "$line" | grep -qiE 'iphone|ipad'; then
      state=$(echo "$line" | grep -oE 'connected|unavailable|disconnected' | head -1)
      if [ "$state" = "connected" ]; then
        id=$(echo "$line" | grep -oE '[0-9A-Fa-f]{8}-[0-9A-Fa-f]{16}|[0-9A-Fa-f-]{36}' | head -1)
        name=$(echo "$line" | awk -F '  +' '{print $1}')
        [ -n "$id" ] && DEVICE_IDS+=("$id") && DEVICE_NAMES+=("$name")
      fi
    fi
  done < <(xcrun devicectl list devices 2>/dev/null)
fi

case "${#DEVICE_IDS[@]}" in
  0)
    echo "❌ No connected iPhone or iPad found."
    echo "   • Plug it in via USB (or make sure it's previously paired over Wi-Fi)"
    echo "   • Unlock the phone and tap \"Trust This Computer\" if prompted"
    echo "   • Settings → Privacy & Security → Developer Mode → on, then reboot the device"
    echo "   Then re-run this script."
    exit 1
    ;;
  1)
    DEVICE_ID="${DEVICE_IDS[0]}"
    echo "📱 Found ${DEVICE_NAMES[0]} — using it automatically"
    ;;
  *)
    echo "📱 Multiple devices found:"
    for i in "${!DEVICE_IDS[@]}"; do
      echo "   $((i+1))) ${DEVICE_NAMES[$i]} (${DEVICE_IDS[$i]})"
    done
    if [ ! -t 0 ]; then
      echo "❌ Can't prompt for a choice (no terminal attached — non-interactive run)."
      echo "   Pass --device <udid> or set DEVICE_ID in .env to pick one of the above."
      exit 1
    fi
    read -r -p "Pick a device [1-${#DEVICE_IDS[@]}]: " choice
    idx=$((choice-1))
    DEVICE_ID="${DEVICE_IDS[$idx]}"
    echo "📱 Using ${DEVICE_NAMES[$idx]}"
    ;;
esac
fi

# --- 3. Build fresh (pinned derivedDataPath -> deterministic .app path) ---
echo "🔨 Building $SCHEME (this always builds fresh — no stale installs)..."
if ! xcodebuild "$PROJECT_FLAG" "$PROJECT" -scheme "$SCHEME" -configuration "$CONFIGURATION" \
      -derivedDataPath "$DERIVED_DATA_PATH" -destination "id=$DEVICE_ID" \
      ${EXTRA_BUILD_ARGS[@]+"${EXTRA_BUILD_ARGS[@]}"} build > "$BUILD_LOG" 2>&1; then
  if grep -qiE "requires a provisioning profile|doesn't match the entitlements|no signing certificate|not registered|no profiles for|provisioning profiles matching|automatic signing is disabled" "$BUILD_LOG"; then
    echo "❌ Build failed — this device likely isn't registered in your provisioning profile yet."
    echo "   Retrying once with -allowProvisioningUpdates..."
    if ! xcodebuild "$PROJECT_FLAG" "$PROJECT" -scheme "$SCHEME" -configuration "$CONFIGURATION" \
          -derivedDataPath "$DERIVED_DATA_PATH" -destination "id=$DEVICE_ID" \
          ${EXTRA_BUILD_ARGS[@]+"${EXTRA_BUILD_ARGS[@]}"} -allowProvisioningUpdates build > "$BUILD_LOG" 2>&1; then
      echo "❌ Still failing. Open Xcode once, select this device in the toolbar, and let it auto-register — then re-run this script."
      tail -20 "$BUILD_LOG"
      exit 1
    fi
  else
    echo "❌ Build failed for a reason unrelated to signing:"
    tail -20 "$BUILD_LOG"
    exit 1
  fi
fi

# --- 4. Derive bundle ID + resolved .app path from the same build settings ---
SETTINGS=$(xcodebuild "$PROJECT_FLAG" "$PROJECT" -scheme "$SCHEME" -configuration "$CONFIGURATION" \
             -derivedDataPath "$DERIVED_DATA_PATH" ${EXTRA_BUILD_ARGS[@]+"${EXTRA_BUILD_ARGS[@]}"} -showBuildSettings 2>/dev/null)
BUNDLE_ID=$(echo "$SETTINGS" | grep -E '^\s*PRODUCT_BUNDLE_IDENTIFIER' | head -1 | awk -F' = ' '{print $2}')
TARGET_BUILD_DIR=$(echo "$SETTINGS" | grep -E '^\s*TARGET_BUILD_DIR' | head -1 | awk -F' = ' '{print $2}')
FULL_PRODUCT_NAME=$(echo "$SETTINGS" | grep -E '^\s*FULL_PRODUCT_NAME' | head -1 | awk -F' = ' '{print $2}')
APP_PATH="$TARGET_BUILD_DIR/$FULL_PRODUCT_NAME"

if [ ! -d "$APP_PATH" ]; then
  echo "❌ Built .app not found at $APP_PATH — the build likely didn't produce a device binary."
  exit 1
fi
echo "✅ Built $FULL_PRODUCT_NAME (bundle id: $BUNDLE_ID)"

# --- 5. Install ---
echo "📲 Installing on device..."
if ! xcrun devicectl device install app --device "$DEVICE_ID" "$APP_PATH" --json-output "$INSTALL_JSON"; then
  echo "❌ Install failed. Common causes: Developer Mode is off, the device is locked, or you tapped \"Don't Trust\" on this Mac."
  exit 1
fi

# --- 6. Force-relaunch (fixes stale UI from an already-running instance) ---
echo "🚀 Relaunching (terminating any already-running instance first)..."
if ! xcrun devicectl device process launch --terminate-existing --device "$DEVICE_ID" "$BUNDLE_ID"; then
  echo "⚠️  Installed successfully but relaunch failed — open the app manually on the phone this one time."
  exit 1
fi

echo "✅ Done — $SCHEME is live on the device with your latest changes."
