#!/bin/bash
#
# derive_offsets.sh — derive CoreIK function offsets for a new sim build
# - Niall George Horn; June 2026

# Info: 
# Usage:
#   ./deploy/derive_offsets.sh                            # uses /tmp/CoreIK.framework/CoreIK (default after setup.sh)
#   ./deploy/derive_offsets.sh <path-to-CoreIK-binary>    # explicit path if needed
#
# setup.sh copies CoreIK to /tmp/CoreIK.framework/ — so in the normal workflow
# you just run setup.sh first and then this script with no arguments.
#
# What it does:
#   Runs `nm --demangle` against the CoreIK binary, greps for the specific
#   private C symbols we call, and prints three blocks ready to paste into:
#     1. deploy/coreik_offsets/<BUILD>.txt       (human-readable record)
#     2. HelloVisionOS/CoreIKBridge.swift        (Swift offsets dict entry)
#     3. cli/main.c                         (C offsets table entry)
#
# After pasting, rebuild the app and run against the new sim — the Swift/C
# offset lookup will pick up the new build automatically via the BUILD file
# that deploy/setup.sh writes to /tmp/CoreIK.framework/BUILD.
#

set -euo pipefail

RED='\033[0;31m'; GREEN='\033[0;32m'; CYAN='\033[0;36m'; BOLD='\033[1m'; RESET='\033[0m'

DEFAULT_BIN="/tmp/CoreIK.framework/CoreIK"

if [ $# -lt 1 ]; then
    COREIK_BIN="$DEFAULT_BIN"
    echo -e "${CYAN}  ℹ  No path given — using default: ${COREIK_BIN}${RESET}"
else
    COREIK_BIN="$1"
fi

[ -f "$COREIK_BIN" ] || { echo -e "${RED}  ❌ Not a file: $COREIK_BIN${RESET}" >&2; echo "  Run ./deploy/setup.sh first, or pass an explicit path." >&2; exit 1; }

# Try to infer the build tag. If the binary is inside /tmp/CoreIK.framework/
# after setup.sh ran, there will be a BUILD file next to it. Otherwise we
# pull from the sibling runtime's SystemVersion.plist by walking up.
BUILD_TAG="?"
# (a) /tmp/CoreIK.framework/BUILD — written by setup.sh; primary source
if [ -f "$(dirname "$COREIK_BIN")/BUILD" ]; then
    BUILD_TAG=$(cat "$(dirname "$COREIK_BIN")/BUILD")
else
    # (b) walk up to find a sibling SystemVersion.plist (works when run
    #     directly against the sim runtime's CoreIK.framework/CoreIK)
    d="$(dirname "$COREIK_BIN")"
    for _ in 1 2 3 4 5 6 7 8; do
        sv="$d/CoreServices/SystemVersion.plist"
        if [ -f "$sv" ]; then
            BUILD_TAG=$(plutil -extract ProductBuildVersion raw -o - "$sv" 2>/dev/null || echo "?")
            break
        fi
        d="$(dirname "$d")"
        [ "$d" = "/" ] && break
    done
fi

# These are the symbols we actually call from Swift / C. Add new ones here
# and they'll be picked up by all three output blocks below automatically.
# Order matters — Swift / C blocks below walk the array.
SYMBOLS=(
    "CreateSolver:_CoreIKCreateSolverFusionInstanceWithEnrollmentLengths"
    "EnableReplay:_CoreIKSolverFusionEnableReplayMode"
    "SetVioPose:_CoreIKSolverFusionSetVioPose"
    "SrcGroupSize:_CoreIKSolverFusionSourceGroupSize"
    "SrcGroupName:_CoreIKSolverFusionSourceGroupGetName"
    "SetSourceV2:_CoreIKSolverFusionSetSourcePoseV2"
    "SolveSources:_CoreIKSolverFusionSolveSources"
    "GetPoseSize:_CoreIKSolverFusionGetOutputPoseSizeV2"
    "GetJointName:_CoreIKSolverFusionGetJointNameV2"
    "Destroy:_CoreIKDestroySolverFusion"
)

# Gather offsets by greping nm output for each symbol.
# nm format: "0000000000009ac80 T _CoreIKCreateSolverFusionInstanceWithEnrollmentLengths"
declare -a FOUND  # entries: "KEY|OFFSET|SYMBOL"
MISSING=0 

NM_OUT=$(nm --demangle "$COREIK_BIN" 2>/dev/null)

for pair in "${SYMBOLS[@]}"; do
    key="${pair%%:*}"; sym="${pair#*:}"
    line=$(echo "$NM_OUT" | grep -E " [TtDdSs] ${sym}$" | head -1 || true)
    if [ -z "$line" ]; then
        FOUND+=("${key}|MISSING|${sym}")
        MISSING=$((MISSING + 1))
        continue
    fi
    off_hex=$(echo "$line" | awk '{print $1}' | sed 's/^0*//')
    # Normalise — leading zeros stripped, prepend 0x
    [ -z "$off_hex" ] && off_hex="0"
    FOUND+=("${key}|0x${off_hex}|${sym}")
done

# =========== Block 1: human-readable record ===========

echo -e "${BOLD}${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
echo -e "${BOLD}  Block 1 — human-readable record${RESET}"
echo -e "${BOLD}           save to: deploy/coreik_offsets/${BUILD_TAG}.txt${RESET}"
echo -e "${BOLD}${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
echo ""
echo "# CoreIK function offsets"
echo "# Build: $BUILD_TAG"
echo "# Binary: $COREIK_BIN"
echo "# Derived: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
echo ""
for entry in "${FOUND[@]}"; do
    IFS='|' read -r key off sym <<< "$entry"
    printf "%-16s = %-12s  (%s)\n" "$key" "$off" "$sym"
done
echo ""

# =========== Block 2: Swift dictionary entry ===========

echo -e "${BOLD}${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
echo -e "${BOLD}  Block 2 — paste into HelloVisionOS/CoreIKBridge.swift${RESET}"
echo -e "${BOLD}           inside the kCoreIKOffsets dictionary${RESET}"
echo -e "${BOLD}${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
echo ""
echo "    \"${BUILD_TAG}\": CoreIKOffsets("
for i in "${!FOUND[@]}"; do
    IFS='|' read -r key off _ <<< "${FOUND[$i]}"
    sep=","
    [ "$i" = "$((${#FOUND[@]} - 1))" ] && sep=""
    printf "        %-14s %s%s\n" "${key}:" "$off" "$sep"
done
echo "    ),"
echo ""

# =========== Block 3: C table entry ===========

echo -e "${BOLD}${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
echo -e "${BOLD}  Block 3 — C table entry (for reference / future CLI use)${RESET}"
echo -e "${BOLD}${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${RESET}"
echo ""
echo "    {"
echo "        .build_tag = \"${BUILD_TAG}\","
for entry in "${FOUND[@]}"; do
    IFS='|' read -r key off _ <<< "$entry"
    printf "        .%-14s = %sUL,\n" "$key" "$off"
done
echo "    },"
echo ""

# =========== Summary ===========

if [ $MISSING -gt 0 ]; then
    echo -e "${RED}  ⚠  ${MISSING} symbol(s) missing — check for rename/ABI break in this build.${RESET}"
    exit 1
fi

echo -e "${GREEN}  ✅ All ${#FOUND[@]} symbols resolved.${RESET}"
echo ""
