#!/usr/bin/env bash
# build-ios.sh — compiles choreo-gui for iOS and stages the artifacts the Xcode
# scaffold (ios/) links against. It runs on ANY host with three behaviors:
#
#   - WITH a Mac/Xcode (xcrun present): full build per target (device + sim),
#     staging the rlib and C static archives for the Xcode project.
#   - WITHOUT Xcode (e.g. the Linux check laptop): the SAME compile stages run
#     via the zig shim set (zig cc provides C compilation; rustc provides all
#     Rust codegen), and the final Apple dylib/app link is SKIPPED with a clear
#     notice — everything short of Apple's linker is verified. This mirrors how
#     scripts/build-android.sh documents its own rustflags/toolchain traps, so
#     read those notes there too.
#
# Staging layout (matches ios/project.yml's LIBRARY_SEARCH_PATHS):
#   target/ios/iphoneos/<profile>/        libchoreo_gui.a  (device slice)
#   target/ios/iphonesimulator/<profile>/ libchoreo_gui.a  (simulator slice)
# Per-SDK because the two SDKs' object formats are incompatible — ld64
# refuses to link a device-built archive into a simulator app (observed in
# CI: "building for 'iOS-simulator', but linking in object file ... built
# for 'iOS'"). project.yml resolves the directory via $(PLATFORM_NAME).
#
# Prerequisites (Mac): rustup targets aarch64-apple-ios[ -sim], Xcode. Set
# IOS_BUILD_STABLE=1 to build with the stable toolchain (CI mode — the
# manifest's nightly-only profile-rustflags block is stripped per-invocation
# via build-stable.sh); default runs use the workspace's pinned nightly.
# Prerequisites (Linux): same rustup targets, zig >= 0.13 on PATH.
#
# NOTE (the -C target-cpu=native trap): the workspace's profile rustflags and
# ~/.cargo/config.toml [build] rustflags both carry host-only codegen flags.
# Profile rustflags are not suppressible via RUSTFLAGS env (see
# build-android.sh's header), so this script installs a RUSTC wrapper that
# strips `-C target-cpu=native` for Apple targets; on Linux it additionally
# routes cc-rs through `zig cc` with a fake SDKROOT so cc-rs never needs
# xcrun, and rewrites cc-rs's iOS C target to zig's macOS target (zig ships
# macOS darwin libc headers but not iOS ones — compile-only difference; the
# final link happens on the Mac regardless).
set -euo pipefail

REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$REPO_ROOT"

PROFILES=() # unused placeholder kept to mirror build-android.sh's structure

log()  { printf '==> %s\n' "$*"; }
fail() { printf 'error: %s\n' "$*" >&2; exit 1; }

# ── Prerequisite checks ──────────────────────────────────────────────────────
command -v cargo >/dev/null || fail "cargo not found"
# Toolchain resolution: stable mode (IOS_BUILD_STABLE=1) runs the build under
# `cargo +stable` (via build-stable.sh), so EVERYTHING below — the rustup
# target check AND the RUSTC shim's real path — must resolve against stable,
# not the workspace's rust-toolchain.toml-pinned nightly. Getting either wrong
# is a CI-visible failure class: targets installed on stable are invisible to
# a default-toolchain `rustup target list --installed` (observed on the
# runner: "rustup target not installed: aarch64-apple-ios"), and a nightly
# rustc under a stable cargo invocation would silently mislabel the build.
TOOLCHAIN_FLAG=""
if [ "${IOS_BUILD_STABLE:-}" = 1 ]; then
    TOOLCHAIN_FLAG="stable"
fi

# rustup is toolchain-selectable per command; cargo is not — the shim's `real`
# path pins the compiler binary cargo's RUSTC env override will use.
RUSTUP="rustup"
[ -n "$TOOLCHAIN_FLAG" ] && RUSTUP="rustup +$TOOLCHAIN_FLAG"
RUSTC_REAL="$(rustc ${TOOLCHAIN_FLAG:++$TOOLCHAIN_FLAG} --print sysroot)/bin/rustc"

# zig is only needed for the non-Mac shim path (Xcode's toolchain serves C
# compilation natively when xcrun is present).
if ! command -v xcrun >/dev/null 2>&1; then
    command -v zig >/dev/null || fail "no xcrun and no zig — install zig for the shim path"
fi

for target in aarch64-apple-ios aarch64-apple-ios-sim; do
    if ! $RUSTUP target list --installed | grep -qx "$target"; then
        fail "rustup target not installed for ${TOOLCHAIN_FLAG:-default} toolchain: $target — run: rustup target add ${TOOLCHAIN_FLAG:+--toolchain $TOOLCHAIN_FLAG }$target"
    fi
done

# ── Shim setup ───────────────────────────────────────────────────────────────
SHIM_DIR="$REPO_ROOT/target/ios-shims"

# Shared cc/cxx shim generator (see the lib for the triple-translation rationale).
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib/ios-cc-shims.sh"

mkdir -p "$SHIM_DIR"

# 1. rustc wrapper: strip `-C target-cpu=native` for Apple targets only.
cat > "$SHIM_DIR/rustc" <<EOF
#!/bin/bash
# Generated by scripts/build-ios.sh — do not edit.
real="$RUSTC_REAL"
has_target=0
for a in "\$@"; do case "\$a" in *apple-ios*) has_target=1 ;; esac; done
if [ \$has_target = 0 ]; then exec "\$real" "\$@"; fi
args=()
for a in "\$@"; do
  if [ "\$a" = "target-cpu=native" ]; then
    if [ "\${#args[@]}" -gt 0 ] && [ "\${args[\${#args[@]}-1]}" = "-C" ]; then
      unset 'args[\${#args[@]}-1]'
    fi
    continue
  fi
  args+=("\$a")
done
exec "\$real" "\${args[@]+"\${args[@]}"}"
EOF

# 2. cc wrapper (NO-MAC PATH ONLY): rewrite cc-rs's C target args to zig's
#    equivalents. Two cases (see the header):
#      - Apple target: cc-rs's iOS triple args are rewritten to zig's macOS
#        target (zig bundles macOS darwin libc headers, not iOS ones —
#        compile-only).
#      - HOST target (x86_64 linux): since choreo-gui gained the iOS-gated
#        choreo-daemon dependency, BUILD scripts in its tree (headless_chrome
#        -> auto_generate_cdp -> ureq/rustls -> ring) compile C for the host
#        during an iOS build, and cc-rs passes a RUST-style host triple
#        (`--target=x86_64-unknown-linux-gnu`) that zig cannot parse. The
#        shim strips rust-style triples and maps the host to zig's
#        x86_64-linux-gnu (zig bundles those libc headers too).
#    NEVER installed/exported when xcrun is present: the Mac's native clang +
#    real SDKs are the correct tools, and the shim breaks them (observed on
#    the runner: "exec: zig: not found" — the shim was exported
#    unconditionally).
#    The generator itself lives in scripts/lib/ios-cc-shims.sh (shared with
#    check-ios.sh so the two cannot drift again; the second argument is
#    stamped into the generated header so a stale shim shows where its
#    current generator lives). The generated files are byte-identical to the
#    pre-refactor heredoc output.
write_ios_cc_shims "$SHIM_DIR" "build-ios.sh"
chmod +x "$SHIM_DIR/rustc"

export RUSTC="$SHIM_DIR/rustc"
# Route bare `cc`/`c++` invocations too: build scripts that spawn the
# compiler from PATH (not via cc-rs) must hit the shim as well, or an
# Apple-target dylib link lands on the HOST compiler and fails
# ("unrecognized command-line option '-framework'").
export PATH="$SHIM_DIR:$PATH"
unset RUSTFLAGS || true # env RUSTFLAGS would poison cross codegen anyway
if command -v xcrun >/dev/null 2>&1; then
    # Mac: native toolchain — do NOT touch CC/CXX/SDKROOT. cc-rs resolves the
    # iOS SDK itself via xcrun; overriding it with the zig shim (or the fake
    # SDK) is what broke the runner build.
    log "using the native Apple C toolchain (no cc shims exported)"
else
    # Linux shim path: fake SDK root satisfies cc-rs's SDK probe (it wants an
    # SDKROOT that exists and doesn't look like a mismatched platform); zig cc
    # tolerates -isysroot pointing at it.
    FAKE_SDK="$REPO_ROOT/target/ios-fake-sdk"
    mkdir -p "$FAKE_SDK/iPhoneOS.platform"
    export CC="$SHIM_DIR/cc" CXX="$SHIM_DIR/cxx" AR="zig ar"
    export SDKROOT="$FAKE_SDK"
fi

# Stable mode (IOS_BUILD_STABLE=1, used by CI): the workspace manifest's
# nightly-only [unstable] profile-rustflags block hard-blocks stable Cargo,
# so the build below is routed through scripts/build-stable.sh, which strips
# exactly those keys for the command's duration (the RUSTC shim then has no
# target-cpu=native to strip — it stays installed because it is harmless and
# the script is toolchain-agnostic).
run_cargo() {
    if [ "${IOS_BUILD_STABLE:-}" = 1 ]; then
        ./scripts/build-stable.sh "$@"
    else
        cargo "$@"
    fi
}

# ── Build + stage ────────────────────────────────────────────────────────────
STAGE_ROOT="$REPO_ROOT/target/ios"
build_one() {
    triple="$1"
    # Debug profile here: the script's job on a non-Mac host is compile
    # validation (fast, no dist-profile fat LTO over the whole Blitz tree).
    # A Mac doing a real app build can pass PROFILE=dist (env) to stage the
    # release artifacts instead.
    profile="${PROFILE:-debug}"
    profile_args=(); [ "$profile" != debug ] && profile_args=(--profile "$profile")
    out="target/$triple/$profile"
    log "building choreo-gui (lib) for $triple ($profile)"
    # cargo rustc --crate-type staticlib: the link input Xcode consumes.
    # WHY NOT the rlib (the earlier approach): Apple's ld64 searches
    # `lib<name>.{dylib,tbd,a}` for `-l<name>` and does NOT look for `.rlib`,
    # so the first CI app-link failed with "library 'choreo_gui' not found".
    # A staticlib is a real `.a` — and crucially it can be produced on ANY
    # host: archive creation is internal to rustc (it pulls in std, the C
    # deps ring/secp256k1, and everything else itself), so no Apple linker is
    # needed on the Linux laptop path either. (NOT --crate-type lib either:
    # the crate's `cdylib` target would also build under `--lib` and the
    # cdylib DOES need Apple's linker.)
    run_cargo rustc -p choreo-gui --lib --crate-type staticlib --target "$triple" \
        "${profile_args[@]+"${profile_args[@]}"}" \
        || fail "build failed for $triple (see output above)"
    # Platform-named stage dir: Xcode's $(PLATFORM_NAME) is `iphoneos` or
    # `iphonesimulator`, NOT the cargo triple (see the header comment).
    case "$triple" in
        *-sim) platform="iphonesimulator" ;;
        *)     platform="iphoneos" ;;
    esac
    stage="$STAGE_ROOT/$platform/$profile"
    mkdir -p "$stage"
    # Clear the stage first: earlier script versions staged rlibs + separate
    # ring/secp archives; a stale mix would confuse the Xcode link.
    rm -f "$stage"/* 2>/dev/null || true
    # The staticlib: the artifact the Xcode scaffold links (-lchoreo_gui
    # via -L). `cargo rustc` stages it at $out/libchoreo_gui.a; keep the
    # deps/ glob fallback for a future `cargo build` layout.
    # NOTE on additional static archives: none are staged separately and
    # deliberately so — the staticlib folds EVERY C dependency into the one
    # .a (rustc archive-creation is internal). New C deps from the tree
    # (e.g. anything choreo-daemon's iOS-gated dependency adds) are picked
    # up automatically; keep it that way rather than growing an explicit
    # ring/secp256k1-style list that rots as deps change.
    alib="$out/libchoreo_gui.a"
    [ -e "$alib" ] || alib="$(ls -1 "$out/deps"/libchoreo_gui-*.a 2>/dev/null | head -n1 || true)"
    [ -n "$alib" ] && [ -e "$alib" ] || fail "no libchoreo_gui.a produced under $out"
    cp "$alib" "$stage/libchoreo_gui.a"
    # The link input is the whole point of staging — verify it is a real
    # ar archive so a truncated/corrupt artifact fails HERE, not as an
    # opaque ld error on the Mac.
    file -b "$stage/libchoreo_gui.a" | grep -q "ar archive" \
        || fail "$stage/libchoreo_gui.a is not an ar archive"
    log "staged $stage ($(du -h "$stage/libchoreo_gui.a" | cut -f1))"
}

# macOS detection: full build possible only when xcrun (⇒ Xcode) is present.
if command -v xcrun >/dev/null 2>&1; then
    log "Xcode detected — full build (device + simulator)"
    build_one aarch64-apple-ios
    build_one aarch64-apple-ios-sim
else
    log "no Xcode on this host — compile-only build (zig shim path)"
    log "the final Apple dylib/app link CANNOT run here; artifacts staged for a Mac"
    build_one aarch64-apple-ios
    log "skipping aarch64-apple-ios-sim (identical codegen; add on the Mac if needed)"
    cat <<'EOF'

WHAT'S NEXT (on a Mac):
  1. rsync the repo (or at least target/ios/) to the Mac
  2. ./scripts/build-ios.sh            # re-runs with xcrun for the sim slice
  3. brew install xcodegen && cd ios && xcodegen generate
  4. open Choreographr.xcodeproj       # phase 0b: verify the event-loop wiring
EOF
fi

log "done"
