#!/usr/bin/env bash
#
# Archive + upload a NucleicRemote iPhone build to App Store Connect (→ TestFlight).
#
#   scripts/ios-release.sh {beta|canary} [--archive-only]
#
# iOS ships two coexisting TestFlight apps — the same channel model as the Mac app, minus the rc
# channel. Each channel is a distinct app record (distinct bundle id) so all can be installed
# side-by-side, exactly like the Mac's separate "Nucleic Beta", "Nucleic Canary", and "Nucleic":
#
#   channel  bundle id                              display name    icon              default bump
#   -------  -------------------------------------  --------------  ----------------  ------------
#   beta     xyz.blakeslee.nucleic-remote.beta      Nucleic Beta    app-logo-beta     minor
#   canary   xyz.blakeslee.nucleic-remote.canary    Nucleic Canary  app-logo-canary   build
#
# The bare base id (xyz.blakeslee.nucleic-remote / "Nucleic" / app-logo-file — the Xcode project
# defaults) is reserved for the stable App Store release, kept separate from the beta app so a beta
# TestFlight build never shares stable's record.
#
# The channel is stamped into Info.plist (NucleicChannel → BuildBanner.swift) and drives the
# per-channel bundle id / display name / app icon via build settings the archive step overrides
# (NUCLEIC_BUNDLE_SUFFIX / NUCLEIC_NAME_SUFFIX / NUCLEIC_APPICON, defaulted in the Xcode project to
# the bare base/stable identity so a plain `xcodebuild`/Xcode build still produces a runnable app).
# The widget extension's bundle id tracks the app's via the same suffix (…$(NUCLEIC_BUNDLE_SUFFIX).widgets).
#
# Prerequisites (one-time — see signing/README.md):
#   • An "Apple Distribution" certificate for the paid team (L7UDTQ6F5W) in the keychain.
#   • BOTH App IDs registered + an app record created in App Store Connect, one per channel:
#       xyz.blakeslee.nucleic-remote.beta (+ .beta.widgets)  and  xyz.blakeslee.nucleic-remote.canary (+ .canary.widgets)
#     (The bare xyz.blakeslee.nucleic-remote (+ .widgets) is the separate stable App Store record.)
#   • An App Store Connect API key (the same one notarization uses), passed by env:
#       NUCLEIC_ASC_KEY_PATH=/path/AuthKey_XXXX.p8
#       NUCLEIC_ASC_KEY_ID=<KeyID>
#       NUCLEIC_ASC_ISSUER_ID=<IssuerID>
#     With the key + -allowProvisioningUpdates, Xcode creates/downloads the distribution profile.
#
# Versioning: iOS keeps its own version state in ios/VERSION (seeded at 0.1.0), shared across both
# channels (as the Mac's ./VERSION is shared across its channels) and bumped by the same
# scripts/bump-version.sh. The default bump is channel-aware, mirroring the Mac: the fast-moving
# canary channel defaults to build-only (build number +1, marketing version unchanged), while beta
# defaults to a minor (we're pre-1.0). NUCLEIC_BUMP overrides either — e.g. BUMP=major|patch. A
# failed run restores ios/VERSION so the build number isn't spent; a successful upload commits it
# so numbers stay monotonic (per app, since the shared counter only ever increases).
#   NUCLEIC_BUMP           semver bump: build | patch | minor | major (default: minor; canary: build)
#   NUCLEIC_VERSION        pin the marketing version (skips the ios/VERSION bump)
#   NUCLEIC_IOS_BUILD      pin the build number      (skips the ios/VERSION bump)
#   NUCLEIC_VERSION_COMMIT commit the ios/VERSION bump on success (default 1; 0 leaves it unstaged)

set -euo pipefail

# Parse args: a channel keyword (beta|canary) and/or the --archive-only flag, in any order.
CHANNEL=""
ARCHIVE_ONLY=0
for arg in "$@"; do
  case "$arg" in
    --archive-only) ARCHIVE_ONLY=1 ;;
    beta|canary)    CHANNEL="$arg" ;;
    *) echo "usage: $0 {beta|canary} [--archive-only]" >&2; exit 2 ;;
  esac
done
CHANNEL="${CHANNEL:-beta}"   # default to the public beta

# Per-channel identity. The suffixes are appended to the base bundle id / app name; the icon is the
# Icon Composer package selected by ASSETCATALOG_COMPILER_APPICON_NAME. Both distributed channels are
# distinct apps layered on the base identity via their own suffix + icon (repo-root .icon packages
# wired into the Xcode project) — mirroring the Mac's separate "Nucleic Beta" and "Nucleic Canary".
# The bare base identity (empty suffixes / app-logo-file, the Xcode project defaults) is reserved for
# the stable App Store release, so a beta build no longer collides with stable.
case "$CHANNEL" in
  beta)   BUNDLE_SUFFIX=".beta";   NAME_SUFFIX=" Beta";    APPICON="app-logo-beta";   DEFAULT_BUMP="minor" ;;
  canary) BUNDLE_SUFFIX=".canary"; NAME_SUFFIX=" Canary";  APPICON="app-logo-canary"; DEFAULT_BUMP="build" ;;
  *) echo "usage: $0 {beta|canary} [--archive-only]" >&2; exit 2 ;;
esac

ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$ROOT"

PROJ="ios/NucleicRemote/NucleicRemote.xcodeproj"
SCHEME="NucleicRemote"
ARCHIVE="$ROOT/dist/ios/NucleicRemote-$CHANNEL.xcarchive"
EXPORT_DIR="$ROOT/dist/ios/export-$CHANNEL"

IOS_VERSION_FILE="$ROOT/ios/VERSION"

# Resolve the version. Default path: bump ios/VERSION (same mechanism as the Mac release) and,
# unless it succeeds, restore it on exit so a failed upload doesn't spend the build number. If
# NUCLEIC_VERSION or NUCLEIC_IOS_BUILD is set, treat it as an explicit pin and leave the file alone.
# The default bump is channel-aware (canary: build-only; beta: minor), overridable via NUCLEIC_BUMP.
BUMP="${NUCLEIC_BUMP:-$DEFAULT_BUMP}"
BUMPED=0
SUCCEEDED=0
RSYNC_SHIM=""
# Single EXIT handler: restore an unspent version bump on failure (so a failed upload doesn't
# skip a build number), and remove the rsync shim dir (set up before the export, below).
cleanup() {
  [ "$SUCCEEDED" = 1 ] || [ "$BUMPED" = 0 ] || git checkout -- ios/VERSION 2>/dev/null || true
  [ -n "$RSYNC_SHIM" ] && rm -rf "$RSYNC_SHIM" 2>/dev/null || true
}
trap cleanup EXIT
if [ -n "${NUCLEIC_VERSION:-}" ] || [ -n "${NUCLEIC_IOS_BUILD:-}" ]; then
  NUCLEIC_MARKETING_VERSION=0.1.0; NUCLEIC_BUILD_NUMBER=0
  # shellcheck source=/dev/null
  [ -f "$IOS_VERSION_FILE" ] && . "$IOS_VERSION_FILE"
  MARKETING_VERSION="${NUCLEIC_VERSION:-$NUCLEIC_MARKETING_VERSION}"
  BUILD_NUMBER="${NUCLEIC_IOS_BUILD:-$NUCLEIC_BUILD_NUMBER}"
  echo "▸ Version (pinned): $MARKETING_VERSION (build $BUILD_NUMBER)"
else
  read -r MARKETING_VERSION BUILD_NUMBER <<EOF
$(NUCLEIC_VERSION_FILE="$IOS_VERSION_FILE" scripts/bump-version.sh "$BUMP")
EOF
  BUMPED=1
  echo "▸ Version: $MARKETING_VERSION (build $BUILD_NUMBER)   [channel=$CHANNEL bump=$BUMP]"
fi

# rsync pin (Xcode exportArchive workaround). Xcode's IPA-packaging step runs the system openrsync
# (/usr/bin/rsync) with Apple's --extended-attributes flag, but openrsync spawns its local copy
# "server" by resolving `rsync` on PATH. If a Homebrew/samba rsync (which doesn't support
# --extended-attributes) is ahead on PATH, the copy dies with
#   rsync: on remote machine: --extended-attributes: unknown option … Copy failed
# and the export fails. Pin `rsync` to the system binary for our xcodebuild subprocesses so both
# ends of the copy are openrsync. Only kicks in when a different rsync is currently shadowing it.
if [ -x /usr/bin/rsync ] && [ "$(command -v rsync 2>/dev/null)" != /usr/bin/rsync ]; then
  RSYNC_SHIM="$(mktemp -d)"
  ln -sf /usr/bin/rsync "$RSYNC_SHIM/rsync"
  echo "▸ Pinning rsync → /usr/bin/rsync (shadowing $(command -v rsync)) for the Xcode export"
  PATH="$RSYNC_SHIM:$PATH"; export PATH
fi

# App Store Connect API-key flags for xcodebuild (provisioning updates + upload).
AUTH=()
if [ -n "${NUCLEIC_ASC_KEY_PATH:-}" ] && [ -n "${NUCLEIC_ASC_KEY_ID:-}" ] && [ -n "${NUCLEIC_ASC_ISSUER_ID:-}" ]; then
  AUTH=(-authenticationKeyPath "$NUCLEIC_ASC_KEY_PATH"
        -authenticationKeyID "$NUCLEIC_ASC_KEY_ID"
        -authenticationKeyIssuerID "$NUCLEIC_ASC_ISSUER_ID")
elif [ "$ARCHIVE_ONLY" -eq 0 ]; then
  cat >&2 <<EOF
ios-release: upload requested but no App Store Connect API key in the environment.

  export NUCLEIC_ASC_KEY_PATH=/path/AuthKey_XXXX.p8
  export NUCLEIC_ASC_KEY_ID=<KeyID>
  export NUCLEIC_ASC_ISSUER_ID=<IssuerID>

Or run with --archive-only to just build the archive (then upload via Xcode Organizer).
EOF
  exit 1
fi

echo "▸ Archiving $SCHEME ($CHANNEL)  (version $MARKETING_VERSION, build $BUILD_NUMBER)"
rm -rf "$ARCHIVE"
xcodebuild \
  -project "$PROJ" \
  -scheme "$SCHEME" \
  -configuration Release \
  -destination 'generic/platform=iOS' \
  -archivePath "$ARCHIVE" \
  -allowProvisioningUpdates \
  "${AUTH[@]}" \
  MARKETING_VERSION="$MARKETING_VERSION" \
  CURRENT_PROJECT_VERSION="$BUILD_NUMBER" \
  NUCLEIC_CHANNEL="$CHANNEL" \
  NUCLEIC_BUNDLE_SUFFIX="$BUNDLE_SUFFIX" \
  NUCLEIC_NAME_SUFFIX="$NAME_SUFFIX" \
  NUCLEIC_APPICON="$APPICON" \
  clean archive

if [ "$ARCHIVE_ONLY" -eq 1 ]; then
  # A real archive with this version now exists; keep the bump (don't let the trap revert it) so a
  # later automated upload won't reuse the same build number, but leave it uncommitted since we
  # didn't upload.
  SUCCEEDED=1
  echo "✓ Archive built: $ARCHIVE"
  echo "  Upload via Xcode → Window → Organizer → Distribute App → App Store Connect."
  [ "$BUMPED" -eq 1 ] && echo "  (ios/VERSION bumped to $MARKETING_VERSION build $BUILD_NUMBER, left uncommitted.)"
  exit 0
fi

echo "▸ Exporting + uploading to App Store Connect (TestFlight)"
rm -rf "$EXPORT_DIR"
xcodebuild -exportArchive \
  -archivePath "$ARCHIVE" \
  -exportOptionsPlist ios/ExportOptions.plist \
  -exportPath "$EXPORT_DIR" \
  -allowProvisioningUpdates \
  "${AUTH[@]}"

# The build is uploaded — the version is "spent". Keep the bump (the EXIT trap no longer reverts
# ios/VERSION) and commit it so build numbers stay monotonic across uploads.
SUCCEEDED=1
if [ "$BUMPED" -eq 1 ] && [ "${NUCLEIC_VERSION_COMMIT:-1}" = "1" ] && ! git diff --quiet -- ios/VERSION 2>/dev/null; then
  git add ios/VERSION
  git commit -q -m "release(ios/$CHANNEL): v$MARKETING_VERSION (build $BUILD_NUMBER)" \
    && echo "▸ Committed iOS version bump → v$MARKETING_VERSION (build $BUILD_NUMBER)"
fi

echo "✓ Uploaded $CHANNEL build $BUILD_NUMBER ($MARKETING_VERSION) to App Store Connect."
echo "  It will appear in TestFlight after processing. Assign it to a tester group there,"
echo "  or submit it for App Store review to ship a release."
