#!/usr/bin/env bash
#
# Cross-compile pydantic-core for Android across all supported ABIs:
#   arm64-v8a   (primary — modern TV hardware)
#   x86_64      (modern emulators)
#   x86         (legacy emulators)
#   armeabi-v7a (32-bit ARMv7 — older cheap TV boxes like X96 mini, MeCool)
#
# Outputs wheels into android/wheels/. Wheels are linked against the real
# libpython3.11.so shipped by Chaquopy (stub .so does NOT work — see
# memory/project_android_app.md for the incident notes).
#
# Prerequisites (on host):
#   - Rust + cargo (rustup) with targets:
#     aarch64/x86_64/i686/armv7a-linux-android(eabi)
#   - Android NDK (ANDROID_NDK_HOME, or installed at Sdk/ndk/*)
#   - Python 3.11 (matches Chaquopy's embedded version)
#   - maturin (pip install maturin)
#
# Rebuild cadence: whenever PYDANTIC_CORE_VERSION changes, or pydantic's
# core dependency version changes.
#
# Usage:
#   ./build-pydantic-core.sh                       # build all 4 ABIs
#   ./build-pydantic-core.sh arm64                 # build a single ABI
#   ./build-pydantic-core.sh arm64 x86_64          # build a subset
#   ./build-pydantic-core.sh armv7                 # 32-bit ARM only
#
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ANDROID_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
WHEELS_DIR="$ANDROID_DIR/wheels"
BUILD_DIR="$ANDROID_DIR/.build-cache"

PYDANTIC_CORE_VERSION="2.46.0"
API_LEVEL=24
PY_VERSION="3.11"

# Resolve a concrete python3.11 interpreter path (maturin needs it callable).
# Windows Git Bash doesn't ship `python3.11` on PATH; fall back to `py -3.11`.
if [ -z "${PYTHON_BIN:-}" ]; then
    # Prefer `py -3.11` on Windows (Git Bash's `python3.11` resolves to a
    # non-functional MSStore stub). Fall back to `python3.11` on Linux/macOS.
    if command -v py >/dev/null 2>&1 && py -3.11 -c '' 2>/dev/null; then
        PYTHON_BIN="$(py -3.11 -c 'import sys; print(sys.executable)' 2>/dev/null | tr -d '\r')"
    elif command -v python3.11 >/dev/null 2>&1 && python3.11 -c '' 2>/dev/null; then
        PYTHON_BIN="$(command -v python3.11)"
    fi
fi
[ -n "${PYTHON_BIN:-}" ] || { echo "ERROR: python3.11 not found; set PYTHON_BIN"; exit 1; }
echo "Python: $PYTHON_BIN"

mkdir -p "$WHEELS_DIR"

# ── Find Android NDK ────────────────────────────────────────────────
if [ -z "${ANDROID_NDK_HOME:-}" ]; then
    for candidate in \
        "$HOME/Library/Android/sdk/ndk"/* \
        "$HOME/Android/Sdk/ndk"/* \
        "${LOCALAPPDATA:-}/Android/Sdk/ndk"/* \
        "/c/Users/$(whoami)/AppData/Local/Android/Sdk/ndk"/* \
        "/usr/local/lib/android/sdk/ndk"/* \
        "${ANDROID_SDK_ROOT:-}/ndk"/*; do
        if [ -d "$candidate" ]; then
            ANDROID_NDK_HOME="$candidate"
            break
        fi
    done
fi
[ -n "${ANDROID_NDK_HOME:-}" ] || { echo "ERROR: NDK not found; set ANDROID_NDK_HOME"; exit 1; }
echo "NDK: $ANDROID_NDK_HOME"

case "$(uname -s)" in
    Linux*)               HOST_TAG="linux-x86_64" ;;
    Darwin*)              HOST_TAG="darwin-x86_64" ;;
    MINGW*|MSYS*|CYGWIN*) HOST_TAG="windows-x86_64" ;;
    *) echo "Unsupported host OS"; exit 1 ;;
esac
TOOLCHAIN="$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/$HOST_TAG"
READELF="$TOOLCHAIN/bin/llvm-readelf"

# ── Prepare source tree ─────────────────────────────────────────────
SRC_DIR="$BUILD_DIR/pydantic_core-$PYDANTIC_CORE_VERSION"
if [ ! -d "$SRC_DIR" ]; then
    echo "Downloading pydantic_core $PYDANTIC_CORE_VERSION sdist..."
    mkdir -p "$BUILD_DIR"
    SDIST="$BUILD_DIR/pydantic_core-$PYDANTIC_CORE_VERSION.tar.gz"
    [ -f "$SDIST" ] || curl -sSL --retry 3 -o "$SDIST" \
        "https://files.pythonhosted.org/packages/source/p/pydantic_core/pydantic_core-$PYDANTIC_CORE_VERSION.tar.gz"
    tar -xzf "$SDIST" -C "$BUILD_DIR"
fi

# ── ABI table ───────────────────────────────────────────────────────
# Columns: short_name rust_target clang_prefix sysconfig_dir
ABI_TABLE=(
    "arm64   aarch64-linux-android    aarch64-linux-android${API_LEVEL}    cross-sysconfig"
    "x86_64  x86_64-linux-android     x86_64-linux-android${API_LEVEL}     cross-sysconfig-x86_64"
    "x86     i686-linux-android       i686-linux-android${API_LEVEL}       cross-sysconfig-x86"
    "armv7   armv7-linux-androideabi  armv7a-linux-androideabi${API_LEVEL} cross-sysconfig-armv7"
)

declare -A ABI_TAG_MAP=(
    [arm64]="arm64_v8a"
    [x86_64]="x86_64"
    [x86]="x86"
    [armv7]="armeabi_v7a"
)

# ── Select which ABIs to build ──────────────────────────────────────
SELECTED=("$@")
if [ ${#SELECTED[@]} -eq 0 ]; then
    SELECTED=(arm64 x86_64 x86 armv7)
fi

# ── Ensure rust targets are installed ───────────────────────────────
for name in "${SELECTED[@]}"; do
    for row in "${ABI_TABLE[@]}"; do
        read -r sname rtarget _ _ <<<"$row"
        if [ "$sname" = "$name" ]; then
            rustup target add "$rtarget" >/dev/null 2>&1 || true
        fi
    done
done

# ── Build loop ──────────────────────────────────────────────────────
cd "$SRC_DIR"

for name in "${SELECTED[@]}"; do
    MATCHED=""
    for row in "${ABI_TABLE[@]}"; do
        read -r sname rtarget cprefix sysdir <<<"$row"
        if [ "$sname" = "$name" ]; then
            MATCHED="1"
            break
        fi
    done
    [ -n "$MATCHED" ] || { echo "Unknown ABI: $name"; exit 1; }

    CROSS_LIB_DIR="$BUILD_DIR/$sysdir"
    [ -f "$CROSS_LIB_DIR/libpython3.11.so" ] || {
        echo "ERROR: missing $CROSS_LIB_DIR/libpython3.11.so (extract from a Chaquopy APK)"
        exit 1
    }

    # On Windows hosts the clang wrapper is a .cmd batch file; cargo/rustc
    # can't exec it as a linker directly (os error 193). Use the .cmd path
    # explicitly so CreateProcess picks up cmd.exe as interpreter.
    CC_BIN="$TOOLCHAIN/bin/${cprefix}-clang"
    if [ "$HOST_TAG" = "windows-x86_64" ] && [ -f "${CC_BIN}.cmd" ]; then
        CC_BIN="${CC_BIN}.cmd"
    fi
    AR_BIN="$TOOLCHAIN/bin/llvm-ar"
    [ "$HOST_TAG" = "windows-x86_64" ] && [ -f "${AR_BIN}.exe" ] && AR_BIN="${AR_BIN}.exe"
    [ -f "$CC_BIN" ] || { echo "ERROR: $CC_BIN not found"; exit 1; }

    # Normalize rust target name for env var (cargo wants UPPER_WITH_UNDERSCORES)
    TARGET_ENV=$(echo "$rtarget" | tr 'a-z-' 'A-Z_')

    echo ""
    echo "════════════════════════════════════════════════════════════"
    echo " Building pydantic_core $PYDANTIC_CORE_VERSION for $name ($rtarget)"
    echo "════════════════════════════════════════════════════════════"

    env \
        CARGO_TARGET_DIR="$SRC_DIR/target" \
        "CC_${rtarget//-/_}=$CC_BIN" \
        "AR_${rtarget//-/_}=$AR_BIN" \
        "CARGO_TARGET_${TARGET_ENV}_LINKER=$CC_BIN" \
        PYO3_CROSS=1 \
        PYO3_CROSS_LIB_DIR="$CROSS_LIB_DIR" \
        PYO3_CROSS_PYTHON_VERSION="$PY_VERSION" \
        RUSTFLAGS="-C link-arg=-Wl,--no-as-needed -C link-arg=-lpython3.11 -C link-arg=-L$CROSS_LIB_DIR" \
        maturin build \
            --release \
            --target "$rtarget" \
            --interpreter "$PYTHON_BIN" \
            --out "$WHEELS_DIR" \
            --compatibility linux

    # maturin writes the wheel with the correct android_<api>_<abi> platform
    # tag directly (the sysconfigdata provides MULTIARCH). Find + verify.
    ABI_TAG="${ABI_TAG_MAP[$name]}"
    OUT_WHL="$WHEELS_DIR/pydantic_core-$PYDANTIC_CORE_VERSION-cp311-cp311-android_${API_LEVEL}_${ABI_TAG}.whl"
    [ -f "$OUT_WHL" ] || { echo "ERROR: expected wheel not found: $OUT_WHL"; exit 1; }

    TMP=$(mktemp -d)
    unzip -qo "$OUT_WHL" -d "$TMP" "pydantic_core/*.so"
    SO=$(find "$TMP" -name "*.so" | head -1)
    if "$READELF" -d "$SO" | grep -q "libpython3.11.so"; then
        echo "  NEEDED OK: libpython3.11.so present in $(basename "$OUT_WHL")"
    else
        echo "  ERROR: libpython3.11.so NOT in NEEDED — wheel will crash at import"
        "$READELF" -d "$SO" | grep NEEDED
        rm -rf "$TMP"
        exit 1
    fi
    rm -rf "$TMP"
done

echo ""
echo "=== Build complete ==="
ls -lh "$WHEELS_DIR"/pydantic_core-*.whl
