#!/bin/bash
# DragonX ImGui Wallet - Unified Build Script
# Copyright 2024-2026 The Hush Developers
# Released under the GPLv3
#
# Usage:
#   ./build.sh                        # Dev build (Linux, debug-friendly)
#   ./build.sh --linux-release        # Linux release (zip + AppImage)
#   ./build.sh --win-release          # Windows cross-compile (mingw-w64)
#   ./build.sh --mac-release          # macOS .app bundle + DMG
#   ./build.sh --linux-release --win-release   # Multiple targets
#   ./build.sh --clean --win-release  # Clean first, then build
#
# Prerequisites:
#   Linux:   cmake, g++, libsdl3-dev (or fetched), libsodium-dev
#   Windows: mingw-w64 (posix threads), cmake
#   macOS (native): Xcode CLT, cmake, create-dmg (brew install create-dmg)
#   macOS (cross from Linux): osxcross + macOS SDK, genisoimage, icnsutils

set -e

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# VERSION is resolved per-variant from CMakeLists.txt (the single source of truth) after arg
# parsing — see the APP_BASENAME block below. Placeholder until then.
VERSION=""

# ── Colours ──────────────────────────────────────────────────────────────────
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m'

info()  { echo -e "${GREEN}[*]${NC} $1"; }
warn()  { echo -e "${YELLOW}[!]${NC} $1"; }
err()   { echo -e "${RED}[ERROR]${NC} $1"; }
header(){ echo -e "\n${CYAN}══════════════════════════════════════════════════════════${NC}"; echo -e "${CYAN}  $1${NC}"; echo -e "${CYAN}══════════════════════════════════════════════════════════${NC}"; }

JOBS=$(nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)

# ── Defaults ─────────────────────────────────────────────────────────────────
DO_DEV=false
DO_LINUX=false
DO_WIN=false
DO_MAC=false
DO_LITE=false
DO_LITE_BACKEND=false
CLEAN=false
BUILD_TYPE="Release"

usage() {
    cat <<EOF
DragonX Wallet — Unified Build Script

Usage: $0 [options]

Targets  (at least one required, or none for dev build):
  --linux-release     Linux release (zip + AppImage)   -> release/linux/
  --win-release       Windows cross-compile (mingw-w64) -> release/windows/
  --mac-release       macOS .app bundle + DMG           -> release/mac/
    --lite              Build ObsidianDragonLite variant (no embedded daemon/full-node features)
    --lite-backend      Like --lite, and link the real SDXL litelib backend artifact
                        (auto-discovers build/lite-backend/<platform>/; build it with
                        scripts/build-lite-backend-artifact.sh, or set DRAGONX_LITE_BACKEND_DIR)

  Build trees are stored under build/{linux,windows,mac}/

Options:
  -c, --clean         Remove build artifacts before building
  -d, --debug         Use Debug build type instead of Release
  -j N                Parallel jobs (default: $JOBS)
  -h, --help          Show this help

Cross-compiling from Linux:
  Windows:  sudo apt install mingw-w64
  macOS:    Requires osxcross (https://github.com/tpoechtrager/osxcross)
            export OSXCROSS=/path/to/osxcross
            sudo apt install genisoimage icnsutils   # DMG + icon tools

Examples:
  $0                             # Quick dev build (Linux)
  $0 --linux-release             # Linux release (zip + AppImage)
  $0 --win-release               # Windows cross-compile
  $0 --mac-release               # macOS bundle + DMG (native or osxcross)
  $0 --lite-backend --mac-release  # macOS ObsidianDragonLite.app + DMG (lite backend)
  $0 --clean --linux-release --win-release   # Clean + both
EOF
    exit 0
}

# ── Parse args ───────────────────────────────────────────────────────────────
while [[ $# -gt 0 ]]; do
    case $1 in
        --linux-release) DO_LINUX=true; shift ;;
        --win-release)   DO_WIN=true;   shift ;;
        --mac-release)   DO_MAC=true;   shift ;;
        --lite)          DO_LITE=true;  shift ;;
        --lite-backend)  DO_LITE=true; DO_LITE_BACKEND=true; shift ;;
        -c|--clean)      CLEAN=true;    shift ;;
        -d|--debug)      BUILD_TYPE="Debug"; shift ;;
        -j)              JOBS="$2"; shift 2 ;;
        -h|--help)       usage ;;
        *) err "Unknown option: $1"; usage ;;
    esac
done

# No release target → dev build (native, no packaging)
if ! $DO_LINUX && ! $DO_WIN && ! $DO_MAC; then
    DO_DEV=true
fi

APP_BASENAME="ObsidianDragon"
CMAKE_LITE_ARGS=()
# Always set the variant flag EXPLICITLY (ON and OFF) so switching variants in a shared build dir
# can't reuse a stale cached value (e.g. a prior --lite build leaving DRAGONX_BUILD_LITE=ON).
if $DO_LITE; then
    APP_BASENAME="ObsidianDragonLite"
    CMAKE_LITE_ARGS+=("-DDRAGONX_BUILD_LITE=ON")
    # Lite never embeds/launches a daemon; set it explicitly too for cache hygiene.
    CMAKE_LITE_ARGS+=("-DDRAGONX_ENABLE_EMBEDDED_DAEMON=OFF")
    info "Lite mode enabled: building ${APP_BASENAME}"
else
    CMAKE_LITE_ARGS+=("-DDRAGONX_BUILD_LITE=OFF")
    # Re-assert the embedded daemon ON for full-node builds, EXPLICITLY, so a build dir whose cache
    # was poisoned OFF by a prior --lite configure (or any stale value) is healed — otherwise the
    # full-node app extracts dragonxd but never launches it (isUsingEmbeddedDaemon() == false).
    CMAKE_LITE_ARGS+=("-DDRAGONX_ENABLE_EMBEDDED_DAEMON=ON")
fi

# Resolve the release version string for the active variant from CMakeLists.txt (single source of
# truth): the full-node app uses project() VERSION + DRAGONX_VERSION_SUFFIX; ObsidianDragonLite uses
# DRAGONX_LITE_VERSION + DRAGONX_LITE_VERSION_SUFFIX.
_cml="$SCRIPT_DIR/CMakeLists.txt"
_full_ver=$(sed -n 's/^[[:space:]]*VERSION[[:space:]][[:space:]]*\([0-9][0-9.]*\).*/\1/p' "$_cml" | head -1)
_full_suffix=$(sed -n 's/^set(DRAGONX_VERSION_SUFFIX[[:space:]]*"\([^"]*\)").*/\1/p' "$_cml" | head -1)
_lite_ver=$(sed -n 's/^set(DRAGONX_LITE_VERSION[[:space:]]*"\([^"]*\)").*/\1/p' "$_cml" | head -1)
_lite_suffix=$(sed -n 's/^set(DRAGONX_LITE_VERSION_SUFFIX[[:space:]]*"\([^"]*\)").*/\1/p' "$_cml" | head -1)
if $DO_LITE; then
    VERSION="${_lite_ver}${_lite_suffix}"
else
    VERSION="${_full_ver}${_full_suffix}"
fi
[ -n "$_full_ver" ] && [ -n "$VERSION" ] || { err "Could not parse version from CMakeLists.txt"; exit 1; }
info "Release version: ${VERSION} (${APP_BASENAME})"

# ── Lite backend (real SDXL litelib) linking ─────────────────────────────────
# Enables DRAGONX_ENABLE_LITE_BACKEND with an imported artifact produced by
# scripts/build-lite-backend-artifact.sh. Auto-discovers build/lite-backend/<platform>/;
# override the directory with DRAGONX_LITE_BACKEND_DIR.
if $DO_LITE_BACKEND; then
    # Artifact platform follows the cross target when exactly one non-host release is requested,
    # so `--lite-backend --win-release` links the Windows backend (not the host's) automatically.
    case "$(uname -s)" in
        Linux)  lb_platform="linux" ;;
        Darwin) lb_platform="macos" ;;
        *)      lb_platform="linux" ;;
    esac
    if $DO_WIN && ! $DO_LINUX && ! $DO_MAC; then lb_platform="windows"; fi
    if $DO_MAC && ! $DO_LINUX && ! $DO_WIN; then lb_platform="macos"; fi
    lb_dir="${DRAGONX_LITE_BACKEND_DIR:-$SCRIPT_DIR/build/lite-backend/$lb_platform}"
    lb_lib=""
    for cand in "$lb_dir"/libsilentdragonxlite.a "$lb_dir"/libsilentdragonxlite.so "$lb_dir"/silentdragonxlite.lib; do
        [[ -f "$cand" ]] && { lb_lib="$cand"; break; }
    done
    lb_symbols="$lb_dir/lite-backend-symbols.txt"
    lb_manifest="$lb_dir/lite-backend-artifact-manifest.json"
    if [[ -z "$lb_lib" || ! -f "$lb_symbols" ]]; then
        err "Lite backend artifact not found under: $lb_dir"
        err "Build it first:  ./scripts/build-lite-backend-artifact.sh --platform $lb_platform"
        err "Or set DRAGONX_LITE_BACKEND_DIR to an existing artifact directory."
        exit 1
    fi
    CMAKE_LITE_ARGS+=(
        "-DDRAGONX_ENABLE_LITE_BACKEND=ON"
        "-DDRAGONX_LITE_BACKEND_LIBRARY=$lb_lib"
        "-DDRAGONX_LITE_BACKEND_SYMBOLS_FILE=$lb_symbols"
        "-DDRAGONX_LITE_BACKEND_LINK_MODE=imported"
        "-DDRAGONX_LITE_BACKEND_ABI=sdxl-c-v1"
    )
    [[ -f "$lb_manifest" ]] && CMAKE_LITE_ARGS+=("-DDRAGONX_LITE_BACKEND_MANIFEST=$lb_manifest")
    # A Rust x86_64-pc-windows-gnu staticlib pulls in Win32 system libs (rustls/schannel, ring,
    # dirs, std) that the app doesn't already link. The set is rustc's `--print native-static-libs`
    # for the backend (winapi_* shims mapped to the real mingw import libs); all exist in mingw-w64.
    if [[ "$lb_platform" == "windows" ]]; then
        CMAKE_LITE_ARGS+=("-DDRAGONX_LITE_BACKEND_EXTRA_LIBS=advapi32;ws2_32;kernel32;bcrypt;cfgmgr32;credui;crypt32;cryptnet;fwpuclnt;gdi32;msimg32;ncrypt;ntdll;ole32;opengl32;secur32;shell32;synchronization;user32;winspool;userenv")
    fi
    info "Lite backend enabled ($lb_platform): $lb_lib"
else
    # Explicit OFF so a prior --lite-backend configure in a shared build dir can't leave it ON
    # (which would then fail the BUILD_LITE=OFF guard in CMake).
    CMAKE_LITE_ARGS+=("-DDRAGONX_ENABLE_LITE_BACKEND=OFF")
fi

should_bundle_full_node_assets() {
    ! $DO_LITE
}

# The offline wallet-rebuild helper is the ONLY thing that repairs a genuinely BDB-inconsistent
# wallet.dat — plain "Restore" just re-triggers the daemon's salvage cascade. A full-node release must
# NEVER ship without it (the in-app "Repair automatically" option silently disappears otherwise), so
# treat a missing helper as a HARD build failure instead of degrading recovery to Restore-only.
#   $1 = built helper path (e.g. bin/dragonx-wallet-rebuild[.exe]);  $2 = the BDB depends dir for the hint.
require_wallet_rebuild_helper() {
    local helper="$1" depends="$2"
    should_bundle_full_node_assets || return 0     # lite builds have no BDB wallet.dat to rebuild
    if [[ ! -f "$helper" ]]; then
        err "wallet-rebuild helper was NOT built: $helper"
        err "  → the recovery 'Repair automatically' option would be MISSING from this release."
        err "  Cause: the vendored Berkeley DB depends are absent, so CMake skipped the dragonx-wallet-rebuild target."
        err "  Fix: provide ${depends}/{lib/libdb-6.2.a,include/db.h} (same static libdb the daemon links), then rebuild."
        exit 1
    fi
    info "  wallet-rebuild helper present: $helper"
}

# Vendored Berkeley DB depends directory for the macOS wallet-rebuild helper, by TARGET arch. The daemon
# links a static libdb-6.2; the helper must match the build arch, so derive the triple from it rather than
# hardcoding one. Single-arch: x86_64 -> x86_64-apple-darwin, arm64 -> aarch64-apple-darwin. "universal"
# (or anything unexpected) falls back to the host arch — a universal .app needs a universal libdb, so if
# only a single-arch libdb is vendored, build single-arch via DRAGONX_MAC_ARCHS.
mac_bdb_dir() {
    local arch="$1" triple
    case "$arch" in
        x86_64) triple="x86_64-apple-darwin" ;;
        arm64)  triple="aarch64-apple-darwin" ;;
        *)      [[ "$(uname -m)" == "arm64" ]] && triple="aarch64-apple-darwin" || triple="x86_64-apple-darwin" ;;
    esac
    printf '%s/external/dragonx/depends/%s\n' "$SCRIPT_DIR" "$triple"
}

# ── Helper: find resource files ──────────────────────────────────────────────
find_sapling_params() {
    local dirs=(
        "$HOME/.zcash-params"
        "$HOME/.hush-params"
        "$SCRIPT_DIR/../SilentDragonX"
        "$SCRIPT_DIR/prebuilt-binaries/dragonxd-win"
        "$SCRIPT_DIR/prebuilt-binaries/dragonxd-linux"
        "$SCRIPT_DIR"
    )
    for d in "${dirs[@]}"; do
        if [[ -f "$d/sapling-spend.params" && -f "$d/sapling-output.params" ]]; then
            SAPLING_SPEND="$d/sapling-spend.params"
            SAPLING_OUTPUT="$d/sapling-output.params"
            info "Found Sapling params in $d"
            return 0
        fi
    done
    warn "Sapling params not found — embedded resources will be unavailable"
    return 1
}

find_asmap() {
    local paths=(
        "$SCRIPT_DIR/external/dragonx/asmap.dat"
        "$SCRIPT_DIR/external/dragonx/contrib/asmap/asmap.dat"
        "$HOME/dragonx/asmap.dat"
        "$HOME/dragonx/contrib/asmap/asmap.dat"
        "$SCRIPT_DIR/../asmap.dat"
        "$SCRIPT_DIR/asmap.dat"
        "$SCRIPT_DIR/../SilentDragonX/asmap.dat"
        "$SCRIPT_DIR/prebuilt-binaries/dragonxd-linux/asmap.dat"
        "$SCRIPT_DIR/prebuilt-binaries/dragonxd-win/asmap.dat"
    )
    for p in "${paths[@]}"; do
        if [[ -f "$p" ]]; then
            ASMAP_DAT="$p"
            info "Found asmap.dat at $p"
            return 0
        fi
    done
    return 1
}

# ── Helper: bundle daemon binaries into a target dir ─────────────────────────
bundle_linux_daemon() {
    local dest="$1"
    local found=0

    local daemon_paths=(
        "$SCRIPT_DIR/prebuilt-binaries/dragonxd-linux/dragonxd"
        "$SCRIPT_DIR/../dragonxd"
        "$SCRIPT_DIR/external/dragonx/src/dragonxd"
        "$HOME/dragonx/src/dragonxd"
    )
    for p in "${daemon_paths[@]}"; do
        if [[ -f "$p" ]]; then
            cp "$p" "$dest/dragonxd"; chmod +x "$dest/dragonxd"
            info "  Bundled dragonxd"; found=1; break
        fi
    done

    local cli_paths=(
        "$SCRIPT_DIR/prebuilt-binaries/dragonxd-linux/dragonx-cli"
        "$SCRIPT_DIR/../dragonx-cli"
        "$SCRIPT_DIR/external/dragonx/src/dragonx-cli"
        "$HOME/dragonx/src/dragonx-cli"
    )
    for p in "${cli_paths[@]}"; do
        if [[ -f "$p" ]]; then
            cp "$p" "$dest/dragonx-cli"; chmod +x "$dest/dragonx-cli"
            info "  Bundled dragonx-cli"; break
        fi
    done

    local dragonxd_paths=(
        "$SCRIPT_DIR/prebuilt-binaries/dragonxd-linux/dragonxd"
        "$SCRIPT_DIR/../dragonxd"
        "$SCRIPT_DIR/external/dragonx/src/dragonxd"
        "$HOME/dragonx/src/dragonxd"
    )
    for p in "${dragonxd_paths[@]}"; do
        if [[ -f "$p" ]]; then
            cp "$p" "$dest/dragonxd"; chmod +x "$dest/dragonxd"
            info "  Bundled dragonxd"; break
        fi
    done

    # asmap.dat
    find_asmap && cp "$ASMAP_DAT" "$dest/asmap.dat" && info "  Bundled asmap.dat"

    # (The dragonx-wallet-rebuild recovery helper is built into bin/ by CMake and packaged explicitly
    #  by each release path — required via require_wallet_rebuild_helper — so it is not copied here.)

    return $found
}

# ═══════════════════════════════════════════════════════════════════════════════
#  DEV BUILD  (native, no packaging)
# ═══════════════════════════════════════════════════════════════════════════════
build_dev() {
    header "Dev Build ($(uname -s) / $BUILD_TYPE)"

    # Use platform-appropriate build directory
    if [[ "$(uname -s)" == "Darwin" ]]; then
        local bd="$SCRIPT_DIR/build/mac"
        export MACOSX_DEPLOYMENT_TARGET="11.0"
    else
        local bd="$SCRIPT_DIR/build/linux"
    fi

    if $CLEAN; then
        info "Cleaning $bd ..."; rm -rf "$bd"
    fi
    mkdir -p "$bd" && cd "$bd"

    info "Configuring ..."
    cmake "$SCRIPT_DIR" \
        -DCMAKE_BUILD_TYPE="$BUILD_TYPE" \
        -DCMAKE_CXX_FLAGS_RELEASE="-O3 -DNDEBUG" \
        -DDRAGONX_USE_SYSTEM_SDL3=ON \
        "${CMAKE_LITE_ARGS[@]}"

    info "Building with $JOBS jobs ..."
    cmake --build . -j "$JOBS"

    [[ -f "bin/${APP_BASENAME}" ]] || { err "Build failed"; exit 1; }
    info "Dev binary: $bd/bin/${APP_BASENAME} ($(du -h "bin/${APP_BASENAME}" | cut -f1))"
}

# ═══════════════════════════════════════════════════════════════════════════════
#  RELEASE: LINUX  —  build + strip + bundle daemon + zip + AppImage
# ═══════════════════════════════════════════════════════════════════════════════
build_release_linux() {
    header "Release: Linux x86_64"
    local bd="$SCRIPT_DIR/build/linux"
    local out="$SCRIPT_DIR/release/linux"

    if $CLEAN; then
        info "Cleaning $bd ..."; rm -rf "$bd"
    fi
    mkdir -p "$bd" && cd "$bd"

    # ── Compile ──────────────────────────────────────────────────────────────
    # Point the wallet-rebuild helper at the vendored static Berkeley DB (same libdb the daemon links)
    # so its output is a v6.2 btree the bundled dragonxd reads. Pass the paths EXPLICITLY (bypasses
    # find_library + its cache); the helper target is simply not built if the depends tree is absent.
    local lin_bdb="$SCRIPT_DIR/external/dragonx/depends/x86_64-unknown-linux-gnu"
    local BDB_ARGS=()
    if [[ -f "$lin_bdb/lib/libdb-6.2.a" && -f "$lin_bdb/include/db.h" ]]; then
        BDB_ARGS=( -DBDB_INCLUDE_DIR="$lin_bdb/include" -DBDB_LIBRARY="$lin_bdb/lib/libdb-6.2.a" )
    elif should_bundle_full_node_assets; then
        err "Vendored Berkeley DB depends missing at $lin_bdb — the wallet-rebuild recovery helper cannot be built."
        err "  A full-node release must ship it; aborting rather than degrading recovery to Restore-only."
        exit 1
    fi
    info "Configuring ..."
    cmake "$SCRIPT_DIR" \
        -DCMAKE_BUILD_TYPE=Release \
        -DCMAKE_CXX_FLAGS_RELEASE="-O3 -DNDEBUG" \
        -DDRAGONX_USE_SYSTEM_SDL3=ON \
        "${BDB_ARGS[@]}" \
        "${CMAKE_LITE_ARGS[@]}"

    info "Building with $JOBS jobs ..."
    cmake --build . -j "$JOBS"

    [[ -f "bin/${APP_BASENAME}" ]] || { err "Linux build failed"; exit 1; }

    # A full-node release MUST include the recovery helper — fail loudly, never ship without it.
    require_wallet_rebuild_helper "bin/dragonx-wallet-rebuild" "$lin_bdb"

    info "Stripping ..."
    strip "bin/${APP_BASENAME}"
    [[ -f "bin/dragonx-wallet-rebuild" ]] && strip "bin/dragonx-wallet-rebuild"
    info "Binary: $(du -h "bin/${APP_BASENAME}" | cut -f1)"

    if should_bundle_full_node_assets; then
        # ── Bundle daemon ────────────────────────────────────────────────────
        bundle_linux_daemon "bin" || warn "Daemon not bundled — wallet-only build"

        # ── Bundle Sapling params ────────────────────────────────────────────
        SAPLING_SPEND="" SAPLING_OUTPUT=""
        find_sapling_params && {
            cp -f "$SAPLING_SPEND"  "bin/sapling-spend.params"
            cp -f "$SAPLING_OUTPUT" "bin/sapling-output.params"
            info "Bundled Sapling params"
        } || warn "Sapling params not found — not bundled"
    else
        info "Lite mode: skipping daemon and Sapling/asmap bundling"
    fi

    # ── Package: release/linux/ ──────────────────────────────────────────────
    # Remove only THIS variant's prior artifacts so full-node and lite releases can coexist in the
    # same output dir (both ObsidianDragon* and ObsidianDragonLite* end up under release/linux/).
    mkdir -p "$out"
    rm -rf "$out/${APP_BASENAME}-"* "$out/${APP_BASENAME}.AppImage"

    local DIST="${APP_BASENAME}-${VERSION}-Linux-x64"
    local dist_dir="$out/$DIST"
    mkdir -p "$dist_dir"

    cp "bin/${APP_BASENAME}" "$dist_dir/"
    if should_bundle_full_node_assets; then
        [[ -f bin/dragonxd ]]                && cp bin/dragonxd "$dist_dir/"
        [[ -f bin/dragonx-cli ]]             && cp bin/dragonx-cli "$dist_dir/"
        [[ -f bin/asmap.dat ]]               && cp bin/asmap.dat "$dist_dir/"
        [[ -f bin/sapling-spend.params ]]    && cp bin/sapling-spend.params "$dist_dir/"
        [[ -f bin/sapling-output.params ]]   && cp bin/sapling-output.params "$dist_dir/"
        # Offline wallet-rebuild recovery helper — required (asserted above); ships next to the app.
        cp bin/dragonx-wallet-rebuild "$dist_dir/" && chmod +x "$dist_dir/dragonx-wallet-rebuild"
        info "  Bundled dragonx-wallet-rebuild"
    fi
    # Bundle xmrig for mining support
    local XMRIG_LINUX="$SCRIPT_DIR/prebuilt-binaries/drg-xmrig/xmrig"
    [[ -f "$XMRIG_LINUX" ]] && { cp "$XMRIG_LINUX" "$dist_dir/"; chmod +x "$dist_dir/xmrig"; info "Bundled xmrig"; } || warn "xmrig not found — mining unavailable in zip"
    cp -r bin/res "$dist_dir/" 2>/dev/null || true

    # ── Zip ──────────────────────────────────────────────────────────────────
    if command -v zip &>/dev/null; then
        (cd "$out" && zip -r "$DIST.zip" "$DIST")
        info "Zip: $out/$DIST.zip ($(du -h "$out/$DIST.zip" | cut -f1))"
    fi
    rm -rf "$dist_dir"

    # ── AppImage (single-file) ───────────────────────────────────────────────
    info "Creating AppImage ..."
    local APPDIR="$bd/AppDir"
    rm -rf "$APPDIR"
    mkdir -p "$APPDIR/usr/bin" "$APPDIR/usr/lib" \
             "$APPDIR/usr/share/applications" \
             "$APPDIR/usr/share/icons/hicolor/256x256/apps" \
             "$APPDIR/usr/share/ObsidianDragon/res"

    cp "bin/${APP_BASENAME}" "$APPDIR/usr/bin/"
    cp -r bin/res/* "$APPDIR/usr/share/ObsidianDragon/res/" 2>/dev/null || true

    if should_bundle_full_node_assets; then
        [[ -f bin/dragonxd ]]    && cp bin/dragonxd "$APPDIR/usr/bin/"
        [[ -f bin/dragonx-cli ]] && cp bin/dragonx-cli "$APPDIR/usr/bin/"
        # Daemon data files must be alongside the daemon binary (usr/bin/)
        # because dragonxd searches relative to its own directory.
        [[ -f bin/asmap.dat ]]             && cp bin/asmap.dat "$APPDIR/usr/bin/"
        [[ -f bin/sapling-spend.params ]]  && cp bin/sapling-spend.params "$APPDIR/usr/bin/"
        [[ -f bin/sapling-output.params ]] && cp bin/sapling-output.params "$APPDIR/usr/bin/"
        # Offline wallet-rebuild recovery helper — required (asserted above); ships next to the app.
        cp bin/dragonx-wallet-rebuild "$APPDIR/usr/bin/" && chmod +x "$APPDIR/usr/bin/dragonx-wallet-rebuild"
    fi
    # Bundle xmrig for mining support
    local XMRIG_LINUX_AI="$SCRIPT_DIR/prebuilt-binaries/drg-xmrig/xmrig"
    [[ -f "$XMRIG_LINUX_AI" ]] && { cp "$XMRIG_LINUX_AI" "$APPDIR/usr/bin/"; chmod +x "$APPDIR/usr/bin/xmrig"; }

    # Desktop entry
    cat > "$APPDIR/usr/share/applications/ObsidianDragon.desktop" <<DESK
[Desktop Entry]
Type=Application
Name=DragonX Wallet
Comment=DragonX Cryptocurrency Wallet
Exec=${APP_BASENAME}
Icon=ObsidianDragon
Categories=Finance;Network;
Terminal=false
StartupNotify=true
DESK
    cp "$APPDIR/usr/share/applications/ObsidianDragon.desktop" "$APPDIR/"

    # Icon
    if [[ -f "$SCRIPT_DIR/res/icons/dragonx-256.png" ]]; then
        cp "$SCRIPT_DIR/res/icons/dragonx-256.png" \
           "$APPDIR/usr/share/icons/hicolor/256x256/apps/ObsidianDragon.png"
    else
        cat > "$APPDIR/ObsidianDragon.svg" <<'SVG'
<?xml version="1.0" encoding="UTF-8"?>
<svg width="256" height="256" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
  <rect width="256" height="256" rx="32" fill="#1a1a1a"/>
  <circle cx="128" cy="128" r="80" fill="none" stroke="#2e7d32" stroke-width="8"/>
  <text x="128" y="145" font-family="Arial,sans-serif" font-size="72" font-weight="bold"
        fill="#4caf50" text-anchor="middle">DX</text>
</svg>
SVG
        if command -v rsvg-convert &>/dev/null; then
            rsvg-convert -w 256 -h 256 "$APPDIR/ObsidianDragon.svg" > \
                "$APPDIR/usr/share/icons/hicolor/256x256/apps/ObsidianDragon.png"
        fi
    fi
    cp "$APPDIR/usr/share/icons/hicolor/256x256/apps/ObsidianDragon.png" "$APPDIR/" 2>/dev/null || \
        cp "$APPDIR/ObsidianDragon.svg" "$APPDIR/ObsidianDragon.png" 2>/dev/null || true

    # AppRun
    cat > "$APPDIR/AppRun" <<APPRUN
#!/bin/bash
SELF=\$(readlink -f "\$0")
HERE=\${SELF%/*}
export DRAGONX_RES_PATH="\${HERE}/usr/share/ObsidianDragon/res"
export LD_LIBRARY_PATH="\${HERE}/usr/lib:\${LD_LIBRARY_PATH}"
cd "\${HERE}/usr/share/ObsidianDragon"
exec "\${HERE}/usr/bin/${APP_BASENAME}" "\$@"
APPRUN
    chmod +x "$APPDIR/AppRun"

    # Bundle SDL3
    for lib in libSDL3.so; do
        local lp
        lp=$(ldconfig -p 2>/dev/null | grep "$lib" | head -1 | awk '{print $NF}')
        [[ -n "$lp" && -f "$lp" ]] && cp "$lp" "$APPDIR/usr/lib/" 2>/dev/null || true
    done
    [[ -f "$bd/_deps/sdl3-build/libSDL3.so" ]] && cp "$bd/_deps/sdl3-build/libSDL3.so"* "$APPDIR/usr/lib/" 2>/dev/null || true

    # appimagetool — pinned to a tagged release and SHA-256 verified before we exec it.
    # The old "continuous" tag is a MOVING build fetched over the network and run on the release
    # builder; a compromised/MITM'd artifact would execute here. Verify, or refuse to package.
    local APPIMAGETOOL_URL="https://github.com/AppImage/appimagetool/releases/download/1.9.0/appimagetool-x86_64.AppImage"
    local APPIMAGETOOL_SHA256="46fdd785094c7f6e545b61afcfb0f3d98d8eab243f644b4b17698c01d06083d1"
    local APPIMAGETOOL=""
    if command -v appimagetool &>/dev/null; then
        APPIMAGETOOL="appimagetool"   # maintainer's own trusted system install
    else
        local at="$bd/appimagetool-x86_64.AppImage"
        # Re-verify any cached copy too; a stale unverified download must not be trusted.
        if [[ ! -f "$at" ]] || ! echo "${APPIMAGETOOL_SHA256}  ${at}" | sha256sum -c --status; then
            info "Downloading appimagetool 1.9.0 (pinned) ..."
            wget -q -O "$at" "$APPIMAGETOOL_URL"
            if ! echo "${APPIMAGETOOL_SHA256}  ${at}" | sha256sum -c --status; then
                err "appimagetool SHA-256 verification failed — refusing to use it"
                rm -f "$at"
                return 1
            fi
            chmod +x "$at"
        fi
        APPIMAGETOOL="$at"
    fi

    local ARCH
    ARCH=$(uname -m)
    cd "$bd"
    ARCH="$ARCH" "$APPIMAGETOOL" "$APPDIR" "${APP_BASENAME}-${VERSION}-${ARCH}.AppImage" 2>/dev/null && {
        cp "${APP_BASENAME}-${VERSION}-${ARCH}.AppImage" "$out/${APP_BASENAME}-${VERSION}.AppImage"
        info "AppImage: $out/${APP_BASENAME}-${VERSION}.AppImage ($(du -h "$out/${APP_BASENAME}-${VERSION}.AppImage" | cut -f1))"
    } || warn "AppImage creation failed — binaries zip still in release/linux/"

    info "Linux release artifacts: $out/"
    ls -lh "$out/"
}

# ═══════════════════════════════════════════════════════════════════════════════
#  RELEASE: WINDOWS  —  mingw-w64 cross-compile + INCBIN embedding
# ═══════════════════════════════════════════════════════════════════════════════
build_release_win() {
    header "Release: Windows x86_64 (cross-compile)"
    local bd="$SCRIPT_DIR/build/windows"
    local out="$SCRIPT_DIR/release/windows"

    if $CLEAN; then
        info "Cleaning $bd ..."; rm -rf "$bd"
    fi
    mkdir -p "$bd" && cd "$bd"

    # ── Find MinGW ───────────────────────────────────────────────────────────
    local MINGW_GCC="" MINGW_GXX=""
    if command -v x86_64-w64-mingw32-gcc-posix &>/dev/null; then
        MINGW_GCC="x86_64-w64-mingw32-gcc-posix"
        MINGW_GXX="x86_64-w64-mingw32-g++-posix"
        info "Using POSIX thread model"
    elif command -v x86_64-w64-mingw32-gcc &>/dev/null; then
        MINGW_GCC="x86_64-w64-mingw32-gcc"
        MINGW_GXX="x86_64-w64-mingw32-g++"
        if x86_64-w64-mingw32-gcc -v 2>&1 | grep -q "posix"; then
            info "Using POSIX thread model"
        else
            warn "Using win32 thread model — may have threading issues"
            warn "Switch: sudo update-alternatives --set x86_64-w64-mingw32-gcc /usr/bin/x86_64-w64-mingw32-gcc-posix"
        fi
    else
        err "mingw-w64 not found!  Install: sudo apt install mingw-w64"
        exit 1
    fi

    # ── Patch libwinpthread + libpthread to remove VERSIONINFO resources ────
    # mingw-w64's libwinpthread.a and libpthread.a each ship a version.o
    # with their own VERSIONINFO ("POSIX WinThreads for Windows") that
    # collides with ours during .rsrc merge, causing Task Manager to show
    # the wrong process description.
    local PATCHED_LIB_DIR="$bd/patched-lib"
    mkdir -p "$PATCHED_LIB_DIR"
    for plib in libwinpthread.a libpthread.a; do
        local SYS_LIB="/usr/x86_64-w64-mingw32/lib/$plib"
        if [[ -f "$SYS_LIB" ]]; then
            cp -f "$SYS_LIB" "$PATCHED_LIB_DIR/$plib"
            x86_64-w64-mingw32-ar d "$PATCHED_LIB_DIR/$plib" version.o 2>/dev/null || true
            info "Patched $plib (removed version.o VERSIONINFO resource)"
        fi
    done

    # ── Toolchain file ───────────────────────────────────────────────────────
    cat > "$bd/mingw-toolchain.cmake" <<TOOLCHAIN
set(CMAKE_SYSTEM_NAME Windows)
set(CMAKE_SYSTEM_PROCESSOR x86_64)
set(CMAKE_C_COMPILER $MINGW_GCC)
set(CMAKE_CXX_COMPILER $MINGW_GXX)
set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres)
set(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_EXE_LINKER_FLAGS "-static -static-libgcc -static-libstdc++ -Wl,-Bstatic,--whole-archive -L$PATCHED_LIB_DIR -lwinpthread -Wl,--no-whole-archive")
set(CMAKE_CXX_FLAGS "\${CMAKE_CXX_FLAGS} -static")
set(CMAKE_C_FLAGS "\${CMAKE_C_FLAGS} -static")
set(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
set(BUILD_SHARED_LIBS OFF)
TOOLCHAIN

    # ── INCBIN embedded resources ────────────────────────────────────────────
    info "Generating embedded resources (INCBIN) ..."
    local GEN="$bd/generated"
    local RES="$GEN/res"
    mkdir -p "$RES"

    SAPLING_SPEND="" SAPLING_OUTPUT="" ASMAP_DAT=""
    find_sapling_params || true
    find_asmap || true

    if [[ -n "$SAPLING_SPEND" && -n "$SAPLING_OUTPUT" ]]; then
        cp -f "$SAPLING_SPEND"  "$RES/sapling-spend.params"
        cp -f "$SAPLING_OUTPUT" "$RES/sapling-output.params"

        cat > "$GEN/embedded_data.h" <<HDR
// Auto-generated — DO NOT EDIT (build.sh --win-release)
#pragma once
#include <cstdint>
#include <cstddef>
#include "incbin.h"

INCBIN(sapling_spend_params,  "$RES/sapling-spend.params");
INCBIN(sapling_output_params, "$RES/sapling-output.params");
HDR

        if [[ -n "$ASMAP_DAT" ]]; then
            cp -f "$ASMAP_DAT" "$RES/asmap.dat"
            echo "INCBIN(asmap_dat, \"$RES/asmap.dat\");" >> "$GEN/embedded_data.h"
        else
            echo 'extern "C" { static const uint8_t* g_asmap_dat_data = nullptr; }' >> "$GEN/embedded_data.h"
            echo 'static const unsigned int g_asmap_dat_size = 0;' >> "$GEN/embedded_data.h"
        fi

        # ── Daemon binaries ──────────────────────────────────────────────
        local DD="$SCRIPT_DIR/prebuilt-binaries/dragonxd-win"
        if should_bundle_full_node_assets; then
            if [[ -d "$DD" && -f "$DD/dragonxd.exe" ]]; then
                info "Embedding daemon binaries ..."
                echo -e "\n#define HAS_EMBEDDED_DAEMON 1\n" >> "$GEN/embedded_data.h"
                for f in dragonxd.exe dragonx-cli.exe dragonx-tx.exe; do
                    local sym=$(echo "$f" | sed 's/[^a-zA-Z0-9]/_/g')
                    if [[ -f "$DD/$f" ]]; then
                        cp -f "$DD/$f" "$RES/$f"
                        info "  Staged $f ($(du -h "$DD/$f" | cut -f1))"
                        echo "INCBIN(${sym}, \"$RES/$f\");" >> "$GEN/embedded_data.h"
                    else
                        echo "extern \"C\" { static const uint8_t* g_${sym}_data = nullptr; }" >> "$GEN/embedded_data.h"
                        echo "static const unsigned int g_${sym}_size = 0;" >> "$GEN/embedded_data.h"
                    fi
                done
            else
                warn "prebuilt-binaries/dragonxd-win/ not found — wallet-only build"
            fi
        else
            info "Lite mode: skipping embedded daemon binaries"
        fi

        # ── Wallet-rebuild recovery helper ───────────────────────────────
        # Built in-tree (not a prebuilt like the daemon), so compile it standalone HERE — before the
        # main app compiles embedded_resources.cpp — and INCBIN it, so a bare, self-extracting
        # ObsidianDragon.exe carries the recovery tool exactly like it does the daemon.
        if should_bundle_full_node_assets; then
            local WBDB="$SCRIPT_DIR/external/dragonx/depends/x86_64-w64-mingw32"
            if [[ -f "$WBDB/lib/libdb-6.2.a" && -f "$WBDB/include/db.h" ]]; then
                info "Compiling + embedding wallet-rebuild helper ..."
                x86_64-w64-mingw32-g++ -std=c++17 -O2 -static -static-libgcc -static-libstdc++ \
                    -I"$SCRIPT_DIR/src" -I"$WBDB/include" \
                    "$SCRIPT_DIR/tools/wallet_rebuild/main.cpp" \
                    "$WBDB/lib/libdb-6.2.a" -lws2_32 \
                    -o "$RES/dragonx-wallet-rebuild.exe" \
                    || { err "wallet-rebuild helper failed to compile for embedding"; exit 1; }
                x86_64-w64-mingw32-strip "$RES/dragonx-wallet-rebuild.exe" 2>/dev/null || true
                echo -e "\n#define HAS_EMBEDDED_WALLET_REBUILD 1" >> "$GEN/embedded_data.h"
                echo "INCBIN(dragonx_wallet_rebuild_exe, \"$RES/dragonx-wallet-rebuild.exe\");" >> "$GEN/embedded_data.h"
                info "  Embedded dragonx-wallet-rebuild.exe ($(du -h "$RES/dragonx-wallet-rebuild.exe" | cut -f1))"
            else
                err "Vendored mingw Berkeley DB missing at $WBDB — cannot embed the wallet-rebuild recovery helper."
                exit 1
            fi
        fi

        # ── xmrig binary (from prebuilt-binaries/drg-xmrig/) ────────────────
        local XMRIG_DIR="$SCRIPT_DIR/prebuilt-binaries/drg-xmrig"
        # The published DRG-XMRig archives ship the binary inside a versioned subdir, not as a flat
        # xmrig.exe. Extract it from the matching win-x64 zip if it isn't already staged — otherwise
        # the embed below never fires (HAS_EMBEDDED_XMRIG stays undefined) and the wallet ships with
        # no miner ("xmrig binary not found" at runtime).
        if [[ ! -f "$XMRIG_DIR/xmrig.exe" ]]; then
            local _xz; _xz=$(ls "$XMRIG_DIR"/drg-xmrig-*-win-x64.zip 2>/dev/null | head -1)
            if [[ -n "$_xz" ]] && command -v unzip >/dev/null 2>&1; then
                local _xtmp; _xtmp=$(mktemp -d)
                # -j flattens the versioned subdir; check the file (not unzip's exit code, which is
                # non-zero if a pattern matches nothing).
                unzip -j -o "$_xz" '*xmrig.exe' -d "$_xtmp" >/dev/null 2>&1 || true
                if [[ -f "$_xtmp/xmrig.exe" ]]; then
                    cp -f "$_xtmp/xmrig.exe" "$XMRIG_DIR/xmrig.exe"
                    info "  Extracted xmrig.exe from $(basename "$_xz")"
                fi
                rm -rf "$_xtmp"
            fi
        fi
        if [[ -f "$XMRIG_DIR/xmrig.exe" ]]; then
            cp -f "$XMRIG_DIR/xmrig.exe" "$RES/xmrig.exe"
            info "  Staged xmrig.exe ($(du -h "$XMRIG_DIR/xmrig.exe" | cut -f1))"
            echo -e "\n#define HAS_EMBEDDED_XMRIG 1" >> "$GEN/embedded_data.h"
            echo "INCBIN(xmrig_exe, \"$RES/xmrig.exe\");" >> "$GEN/embedded_data.h"
        else
            echo 'extern "C" { static const uint8_t* g_xmrig_exe_data = nullptr; }' >> "$GEN/embedded_data.h"
            echo 'static const unsigned int g_xmrig_exe_size = 0;' >> "$GEN/embedded_data.h"
        fi

        # ── Theme images ─────────────────────────────────────────────────
        echo -e "\n// ---- Embedded theme images ----" >> "$GEN/embedded_data.h"
        local IMAGE_TABLE="" IMAGE_COUNT=0
        for dir in "$SCRIPT_DIR/res/img/backgrounds/texture" "$SCRIPT_DIR/res/img/backgrounds/gradient" "$SCRIPT_DIR/res/img/logos"; do
            [[ -d "$dir" ]] || continue
            for img in "$dir"/*.png; do
                [[ -f "$img" ]] || continue
                local bn=$(basename "$img")
                local sym=$(echo "$bn" | sed 's/[^a-zA-Z0-9]/_/g')
                cp -f "$img" "$RES/$bn"
                echo "INCBIN(img_${sym}, \"$RES/$bn\");" >> "$GEN/embedded_data.h"
                IMAGE_TABLE+="    { g_img_${sym}_data, g_img_${sym}_size, \"${bn}\" },\n"
                IMAGE_COUNT=$((IMAGE_COUNT + 1))
            done
        done
        echo "" >> "$GEN/embedded_data.h"
        echo "#define HAS_EMBEDDED_IMAGES 1" >> "$GEN/embedded_data.h"
        echo "#define EMBEDDED_IMAGE_COUNT $IMAGE_COUNT" >> "$GEN/embedded_data.h"
        echo "#define HAS_EMBEDDED_GRADIENT 1" >> "$GEN/embedded_data.h"
        echo "#define HAS_EMBEDDED_LOGO 1" >> "$GEN/embedded_data.h"
        # Backward compat aliases
        echo "static const uint8_t* g_dark_gradient_png_data = g_img_dark_gradient_png_data;" >> "$GEN/embedded_data.h"
        echo "static const unsigned int g_dark_gradient_png_size = g_img_dark_gradient_png_size;" >> "$GEN/embedded_data.h"
        echo "static const uint8_t* g_logo_ObsidianDragon_dark_png_data = g_img_logo_ObsidianDragon_dark_png_data;" >> "$GEN/embedded_data.h"
        echo "static const unsigned int g_logo_ObsidianDragon_dark_png_size = g_img_logo_ObsidianDragon_dark_png_size;" >> "$GEN/embedded_data.h"
        echo "" >> "$GEN/embedded_data.h"
        echo "struct EmbeddedImageEntry { const uint8_t* data; unsigned int size; const char* filename; };" >> "$GEN/embedded_data.h"
        echo "static const EmbeddedImageEntry s_embedded_images[] = {" >> "$GEN/embedded_data.h"
        echo -e "$IMAGE_TABLE" >> "$GEN/embedded_data.h"
        echo "    { nullptr, 0, nullptr }" >> "$GEN/embedded_data.h"
        echo "};" >> "$GEN/embedded_data.h"

        # ── Overlay themes ───────────────────────────────────────────────
        # Expand skin files with layout sections from ui.toml before embedding
        echo -e "\n// ---- Bundled overlay themes ----" >> "$GEN/embedded_data.h"
        local THEME_STAGE_DIR="$bd/_expanded_themes"
        mkdir -p "$THEME_STAGE_DIR"
        python3 "$SCRIPT_DIR/scripts/expand_themes.py" "$SCRIPT_DIR/res/themes" "$THEME_STAGE_DIR"
        local THEME_TABLE="" THEME_COUNT=0
        for tf in "$THEME_STAGE_DIR"/*.toml; do
            local tbn=$(basename "$tf")
            [[ "$tbn" == "ui.toml" ]] && continue
            local tsym=$(echo "$tbn" | sed 's/[^a-zA-Z0-9]/_/g')
            cp -f "$tf" "$RES/$tbn"
            echo "INCBIN(theme_${tsym}, \"$RES/$tbn\");" >> "$GEN/embedded_data.h"
            THEME_TABLE+="    { g_theme_${tsym}_data, g_theme_${tsym}_size, \"${tbn}\" },\n"
            THEME_COUNT=$((THEME_COUNT + 1))
        done
        echo "" >> "$GEN/embedded_data.h"
        echo "#define EMBEDDED_THEME_COUNT $THEME_COUNT" >> "$GEN/embedded_data.h"
        echo "struct EmbeddedThemeEntry { const uint8_t* data; unsigned int size; const char* filename; };" >> "$GEN/embedded_data.h"
        echo "static const EmbeddedThemeEntry s_embedded_themes[] = {" >> "$GEN/embedded_data.h"
        echo -e "$THEME_TABLE" >> "$GEN/embedded_data.h"
        echo "    { nullptr, 0, nullptr }" >> "$GEN/embedded_data.h"
        echo "};" >> "$GEN/embedded_data.h"

        info "Embedded resources header generated"
    else
        warn "Building WITHOUT embedded resources (Sapling params not found)"
    fi

    # ── Fetch libsodium for Windows if needed ──────────────────────────────
    if [[ ! -f "$SCRIPT_DIR/libs/libsodium-win/lib/libsodium.a" ]]; then
        info "Fetching libsodium for Windows ..."
        "$SCRIPT_DIR/scripts/fetch-libsodium.sh" --win
    fi

    # ── FreeType for Windows (color-emoji rendering) ───────────────────────
    # The mingw toolchain ships no FreeType; cross-build a minimal static one (COLR/CPAL, no external
    # deps). Failure is non-fatal — the wallet just falls back to monochrome emoji.
    local FT_MINGW_PREFIX="$SCRIPT_DIR/third_party/freetype-mingw"
    if [[ ! -f "$FT_MINGW_PREFIX/lib/libfreetype.a" ]]; then
        info "Cross-building FreeType for Windows (color emoji) ..."
        "$SCRIPT_DIR/scripts/build-freetype-mingw.sh" "$FT_MINGW_PREFIX" \
            || warn "FreeType cross-build failed — Windows build will use monochrome emoji"
    fi
    local FT_CMAKE_ARG=()
    if [[ -f "$FT_MINGW_PREFIX/lib/libfreetype.a" ]]; then
        FT_CMAKE_ARG=(-DDRAGONX_MINGW_FREETYPE_PREFIX="$FT_MINGW_PREFIX")
    fi

    # ── CMake + build ────────────────────────────────────────────────────────
    # The wallet-rebuild helper links the vendored mingw static Berkeley DB (the mingw toolchain's
    # find_library is sysroot-only, so pass the depends paths EXPLICITLY to bypass the search). Only
    # enabled if the depends tree is present; guarded with -DBDB_* left empty otherwise.
    local win_bdb="$SCRIPT_DIR/external/dragonx/depends/x86_64-w64-mingw32"
    local BDB_ARGS=()
    if [[ -f "$win_bdb/lib/libdb-6.2.a" && -f "$win_bdb/include/db.h" ]]; then
        BDB_ARGS=( -DBDB_INCLUDE_DIR="$win_bdb/include" -DBDB_LIBRARY="$win_bdb/lib/libdb-6.2.a" )
    elif should_bundle_full_node_assets; then
        err "Vendored Berkeley DB depends missing at $win_bdb — the wallet-rebuild recovery helper cannot be built."
        err "  A full-node release must ship it; aborting rather than degrading recovery to Restore-only."
        exit 1
    fi
    info "Configuring (cross-compile) ..."
    cmake "$SCRIPT_DIR" \
        -DCMAKE_TOOLCHAIN_FILE="$bd/mingw-toolchain.cmake" \
        -DCMAKE_BUILD_TYPE=Release \
        -DDRAGONX_USE_SYSTEM_SDL3=OFF \
        "${BDB_ARGS[@]}" \
        "${FT_CMAKE_ARG[@]}" \
        "${CMAKE_LITE_ARGS[@]}"

    info "Building with $JOBS jobs ..."
    cmake --build . -j "$JOBS"

    [[ -f "bin/${APP_BASENAME}.exe" ]] || { err "Windows build failed"; exit 1; }
    # Strip the app exe — the Linux and macOS release paths already strip theirs, and even the Windows
    # helper (dragonx-wallet-rebuild.exe) is stripped, but the main app exe was shipping unstripped
    # (~5MB of symbols on the full node, ~19MB on the params-heavy lite build). Best-effort.
    x86_64-w64-mingw32-strip --strip-all "bin/${APP_BASENAME}.exe" 2>/dev/null \
        || warn "  strip unavailable — shipping unstripped ${APP_BASENAME}.exe"
    info "Binary: $(du -h "bin/${APP_BASENAME}.exe" | cut -f1)"

    # A full-node release MUST include the recovery helper — fail loudly, never ship without it.
    require_wallet_rebuild_helper "bin/dragonx-wallet-rebuild.exe" "$win_bdb"

    # ── Package: release/windows/ ────────────────────────────────────────────
    # Remove only THIS variant's prior artifacts so full-node and lite releases coexist here.
    mkdir -p "$out"
    rm -rf "$out/${APP_BASENAME}-"* "$out/${APP_BASENAME}.exe"

    local DIST="${APP_BASENAME}-${VERSION}-Windows-x64"
    local dist_dir="$out/$DIST"
    mkdir -p "$dist_dir"
    cp "bin/${APP_BASENAME}.exe" "$dist_dir/"

    local DD="$SCRIPT_DIR/prebuilt-binaries/dragonxd-win"
    if should_bundle_full_node_assets; then
        for f in dragonxd.exe dragonx-cli.exe dragonx-tx.exe; do
            [[ -f "$DD/$f" ]] && cp "$DD/$f" "$dist_dir/"
        done
        # dragonx-wallet-rebuild helper (offline recovery for a BDB-inconsistent wallet.dat) — required.
        cp "bin/dragonx-wallet-rebuild.exe" "$dist_dir/" && info "  Bundled dragonx-wallet-rebuild.exe"
        [[ -f "$dist_dir/dragonx-wallet-rebuild.exe" ]] || { err "Failed to bundle dragonx-wallet-rebuild.exe"; exit 1; }

        # Bundle Sapling params + asmap for the zip distribution
        # (The single-file exe has these embedded via INCBIN, but the zip
        # needs them on disk so the daemon can find them in its work dir.)
        for f in sapling-spend.params sapling-output.params asmap.dat; do
            [[ -f "$DD/$f" ]] && cp "$DD/$f" "$dist_dir/"
        done
    else
        info "Lite mode: skipping daemon and Sapling/asmap assets in Windows zip"
    fi

    # Bundle xmrig for mining support
    local XMRIG_WIN="$SCRIPT_DIR/prebuilt-binaries/drg-xmrig/xmrig.exe"
    [[ -f "$XMRIG_WIN" ]] && { cp "$XMRIG_WIN" "$dist_dir/"; info "Bundled xmrig.exe"; } || warn "xmrig.exe not found — mining unavailable in zip"

    cp -r bin/res "$dist_dir/" 2>/dev/null || true

    # ── Single-file exe (all resources embedded) ────────────────────────────
    cp "bin/${APP_BASENAME}.exe" "$out/${APP_BASENAME}-${VERSION}.exe"
    info "Single-file exe: $out/${APP_BASENAME}-${VERSION}.exe ($(du -h "$out/${APP_BASENAME}-${VERSION}.exe" | cut -f1))"

    # ── Zip ──────────────────────────────────────────────────────────────────
    if command -v zip &>/dev/null; then
        (cd "$out" && zip -r "$DIST.zip" "$DIST")
        info "Zip: $out/$DIST.zip ($(du -h "$out/$DIST.zip" | cut -f1))"
    fi
    rm -rf "$dist_dir"

    info "Windows release artifacts: $out/"
    ls -lh "$out/"
}

# ═══════════════════════════════════════════════════════════════════════════════
#  RELEASE: macOS  —  .app bundle + DMG
#
#  Cross-compile from Linux:
#    1. Install osxcross: https://github.com/tpoechtrager/osxcross
#       export OSXCROSS="$HOME/osxcross"   (or wherever you installed it)
#    2. For DMG: sudo apt install genisoimage
#    3. For .icns icons: sudo apt install icnsutils
#    4. Place macOS daemon binaries in prebuilt-binaries/dragonxd-mac/ (optional)
#
#  Native on macOS:
#    Works out of the box. brew install create-dmg for prettier DMGs.
# ═══════════════════════════════════════════════════════════════════════════════
build_release_mac() {
    local IS_CROSS=false
    local MAC_ARCH="x86_64"

    # Detect cross-compilation from Linux
    if [[ "$(uname -s)" == "Linux" ]]; then
        IS_CROSS=true
        info "Cross-compiling for macOS from Linux"

        # Find osxcross
        if [[ -z "${OSXCROSS:-}" ]]; then
            # Try common locations
            for try_path in "$SCRIPT_DIR/external/osxcross" "$HOME/osxcross" "/opt/osxcross" "/usr/local/osxcross"; do
                if [[ -d "$try_path/target" ]]; then
                    OSXCROSS="$try_path"
                    break
                fi
            done
        fi

        if [[ -z "${OSXCROSS:-}" || ! -d "${OSXCROSS}/target" ]]; then
            err "osxcross not found!  Set OSXCROSS=/path/to/osxcross or install it:"
            echo ""
            echo "  git clone https://github.com/tpoechtrager/osxcross"
            echo "  cd osxcross"
            echo "  # Place MacOSX SDK (e.g. MacOSX13.0.sdk.tar.xz) in tarballs/"
            echo "  UNATTENDED=1 ./build.sh"
            echo "  export OSXCROSS=\$PWD"
            echo ""
            exit 1
        fi
        info "Using osxcross at: $OSXCROSS"
        export PATH="$OSXCROSS/target/bin:$PATH"

        # Find the right compiler triple
        local OSXCROSS_CC="" OSXCROSS_CXX="" OSXCROSS_TRIPLE=""

        # Look for full-triple compilers first (e.g. x86_64-apple-darwin22.4-clang++)
        local found_triple
        found_triple=$(ls "$OSXCROSS/target/bin/"x86_64-apple-darwin*-clang++ 2>/dev/null | head -1)
        if [[ -n "$found_triple" ]]; then
            OSXCROSS_TRIPLE=$(basename "$found_triple" | sed 's/-clang++$//')
            OSXCROSS_CC="$OSXCROSS/target/bin/${OSXCROSS_TRIPLE}-clang"
            OSXCROSS_CXX="$OSXCROSS/target/bin/${OSXCROSS_TRIPLE}-clang++"
            MAC_ARCH="x86_64"
        fi
        # Prefer arm64 if available (Apple Silicon)
        found_triple=$(ls "$OSXCROSS/target/bin/"aarch64-apple-darwin*-clang++ 2>/dev/null | head -1)
        if [[ -n "$found_triple" ]]; then
            OSXCROSS_TRIPLE=$(basename "$found_triple" | sed 's/-clang++$//')
            OSXCROSS_CC="$OSXCROSS/target/bin/${OSXCROSS_TRIPLE}-clang"
            OSXCROSS_CXX="$OSXCROSS/target/bin/${OSXCROSS_TRIPLE}-clang++"
            MAC_ARCH="arm64"
        fi
        # Also try o64/oa64 wrapper — but resolve the underlying triple
        if [[ -z "$OSXCROSS_CXX" ]]; then
            if command -v o64-clang++ &>/dev/null; then
                # Resolve the x86_64 triple from binutils
                OSXCROSS_TRIPLE=$(ls "$OSXCROSS/target/bin/"x86_64-apple-darwin*-ar 2>/dev/null | head -1 | xargs basename | sed 's/-ar$//')
                OSXCROSS_CC="o64-clang"
                OSXCROSS_CXX="o64-clang++"
                MAC_ARCH="x86_64"
            elif command -v oa64-clang++ &>/dev/null; then
                OSXCROSS_TRIPLE=$(ls "$OSXCROSS/target/bin/"aarch64-apple-darwin*-ar 2>/dev/null | head -1 | xargs basename | sed 's/-ar$//')
                OSXCROSS_CC="oa64-clang"
                OSXCROSS_CXX="oa64-clang++"
                MAC_ARCH="arm64"
            fi
        fi

        if [[ -z "$OSXCROSS_CXX" ]]; then
            err "Could not find osxcross compilers in PATH"
            echo "  Ensure \$OSXCROSS/target/bin contains *-apple-darwin*-clang++"
            exit 1
        fi
        info "macOS cross-compiler: $OSXCROSS_CXX (arch: $MAC_ARCH)"
    else
        # Native macOS: build universal (arm64 + x86_64) by default. Override with
        # DRAGONX_MAC_ARCHS (e.g. "x86_64").
        MAC_ARCHS="${DRAGONX_MAC_ARCHS:-arm64;x86_64}"
        # When linking the real lite backend, the app can only include architectures
        # the backend static library actually provides. Its pinned ring 0.16.11 has no
        # Apple-Silicon assembly, so that artifact is x86_64-only — constrain the app
        # arch to the backend's (unless the user explicitly forced DRAGONX_MAC_ARCHS),
        # otherwise the arm64 slice fails to link.
        if $DO_LITE_BACKEND && [[ -z "${DRAGONX_MAC_ARCHS:-}" && -n "${lb_lib:-}" ]] && command -v lipo &>/dev/null; then
            local _backend_archs; _backend_archs=$(lipo -archs "$lb_lib" 2>/dev/null | tr ' ' ';')
            if [[ -n "$_backend_archs" && "$_backend_archs" != "$MAC_ARCHS" ]]; then
                warn "Lite backend provides only [$_backend_archs] — building the app for that instead of universal."
                MAC_ARCHS="$_backend_archs"
            fi
        fi
        if [[ "$MAC_ARCHS" == *";"* || "$MAC_ARCHS" == *","* ]]; then
            MAC_ARCH="universal"
        else
            MAC_ARCH="$MAC_ARCHS"
        fi
        export MACOSX_DEPLOYMENT_TARGET="11.0"
    fi

    header "Release: macOS ($MAC_ARCH$(${IS_CROSS} && echo ' — cross-compile'))"
    local bd="$SCRIPT_DIR/build/mac"
    local out="$SCRIPT_DIR/release/mac"

    if $CLEAN; then
        info "Cleaning $bd ..."; rm -rf "$bd"
    fi
    mkdir -p "$bd" && cd "$bd"

    # ── Compile ──────────────────────────────────────────────────────────────
    if $IS_CROSS; then
        # OSXCROSS_TRIPLE already resolved during compiler detection above
        local OSXCROSS_SDK_PATH
        OSXCROSS_SDK_PATH=$(ls -d "$OSXCROSS"/target/SDK/MacOSX*.sdk 2>/dev/null | head -1)

        # Generate osxcross toolchain file
        cat > "$bd/osxcross-toolchain.cmake" <<TOOLCHAIN
set(CMAKE_SYSTEM_NAME Darwin)
set(CMAKE_SYSTEM_PROCESSOR ${MAC_ARCH})

# Compilers
set(CMAKE_C_COMPILER   ${OSXCROSS_CC})
set(CMAKE_CXX_COMPILER ${OSXCROSS_CXX})
set(CMAKE_OBJC_COMPILER ${OSXCROSS_CC})
set(CMAKE_OBJCXX_COMPILER ${OSXCROSS_CXX})

# SDK sysroot
set(CMAKE_OSX_SYSROOT "${OSXCROSS_SDK_PATH}")

# Cross-compilation binutils
set(CMAKE_AR              "${OSXCROSS}/target/bin/${OSXCROSS_TRIPLE}-ar"                CACHE FILEPATH "ar")
set(CMAKE_RANLIB           "${OSXCROSS}/target/bin/${OSXCROSS_TRIPLE}-ranlib"            CACHE FILEPATH "ranlib")
set(CMAKE_STRIP            "${OSXCROSS}/target/bin/${OSXCROSS_TRIPLE}-strip"             CACHE FILEPATH "strip")
set(CMAKE_INSTALL_NAME_TOOL "${OSXCROSS}/target/bin/${OSXCROSS_TRIPLE}-install_name_tool" CACHE FILEPATH "install_name_tool")
set(CMAKE_OTOOL            "${OSXCROSS}/target/bin/${OSXCROSS_TRIPLE}-otool"             CACHE FILEPATH "otool")
set(CMAKE_LIBTOOL          "${OSXCROSS}/target/bin/${OSXCROSS_TRIPLE}-libtool"           CACHE FILEPATH "libtool")

# Search paths
set(CMAKE_FIND_ROOT_PATH
  "${OSXCROSS_SDK_PATH}"
  "${OSXCROSS}/target/macports/pkgs"
  "${OSXCROSS}/target/macports/pkgs/opt/local"
)
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)

set(CMAKE_OSX_DEPLOYMENT_TARGET 11.0)
TOOLCHAIN

        # Find compiler-rt builtins for macOS
        local COMPILER_RT=""
        local CLANG_RESOURCE_DIR
        CLANG_RESOURCE_DIR=$("$OSXCROSS_CC" -print-resource-dir 2>/dev/null || echo "")
        if [[ -n "$CLANG_RESOURCE_DIR" && -f "$CLANG_RESOURCE_DIR/lib/darwin/libclang_rt.osx.a" ]]; then
            COMPILER_RT="$CLANG_RESOURCE_DIR/lib/darwin/libclang_rt.osx.a"
        elif [[ -f "$OSXCROSS/build/compiler-rt/compiler-rt/build/lib/darwin/libclang_rt.osx.a" ]]; then
            COMPILER_RT="$OSXCROSS/build/compiler-rt/compiler-rt/build/lib/darwin/libclang_rt.osx.a"
        fi

        # Fetch libsodium for macOS if needed
        if [[ ! -f "$SCRIPT_DIR/libs/libsodium-mac/lib/libsodium.a" ]]; then
            info "Fetching libsodium for macOS ..."
            "$SCRIPT_DIR/scripts/fetch-libsodium.sh" --mac
        fi

        info "Configuring (cross-compile via osxcross) ..."
        # Vendored static libdb for the recovery helper (full-node only), by target arch; see mac_bdb_dir.
        local mac_bdb; mac_bdb="$(mac_bdb_dir "$MAC_ARCH")"
        local BDB_ARGS=()
        if should_bundle_full_node_assets && [[ -f "$mac_bdb/lib/libdb-6.2.a" && -f "$mac_bdb/include/db.h" ]]; then
            BDB_ARGS=( -DBDB_INCLUDE_DIR="$mac_bdb/include" -DBDB_LIBRARY="$mac_bdb/lib/libdb-6.2.a" )
        fi
        cmake "$SCRIPT_DIR" \
            -DCMAKE_TOOLCHAIN_FILE="$bd/osxcross-toolchain.cmake" \
            -DCMAKE_BUILD_TYPE=Release \
            -DCMAKE_CXX_FLAGS_RELEASE="-O3 -DNDEBUG" \
            -DDRAGONX_USE_SYSTEM_SDL3=OFF \
            -DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 \
            ${COMPILER_RT:+-DOSXCROSS_COMPILER_RT="$COMPILER_RT"} \
            "${BDB_ARGS[@]}" \
            "${CMAKE_LITE_ARGS[@]}"
    else
        # Build libsodium as universal if needed
        local need_sodium=false
        if [[ ! -f "$SCRIPT_DIR/libs/libsodium/lib/libsodium.a" ]] && \
           [[ ! -f "$SCRIPT_DIR/libs/libsodium-mac/lib/libsodium.a" ]]; then
            need_sodium=true
        elif [[ -f "$SCRIPT_DIR/libs/libsodium/lib/libsodium.a" ]]; then
            # Rebuild if existing lib is not universal (single-arch won't link)
            if ! lipo -info "$SCRIPT_DIR/libs/libsodium/lib/libsodium.a" 2>/dev/null | grep -Eq "arm64.*x86_64|x86_64.*arm64"; then
                info "Existing libsodium is not universal — rebuilding ..."
                rm -rf "$SCRIPT_DIR/libs/libsodium"
                need_sodium=true
            fi
        fi
        if $need_sodium; then
            info "Building libsodium (universal) ..."
            "$SCRIPT_DIR/scripts/fetch-libsodium.sh"
        fi

        info "Configuring (native macOS, arch: $MAC_ARCHS) ..."
        # Point CMake at the vendored static libdb for the recovery helper (full-node only); see mac_bdb_dir.
        local mac_bdb; mac_bdb="$(mac_bdb_dir "$MAC_ARCH")"
        local BDB_ARGS=()
        if should_bundle_full_node_assets && [[ -f "$mac_bdb/lib/libdb-6.2.a" && -f "$mac_bdb/include/db.h" ]]; then
            BDB_ARGS=( -DBDB_INCLUDE_DIR="$mac_bdb/include" -DBDB_LIBRARY="$mac_bdb/lib/libdb-6.2.a" )
        fi
        cmake "$SCRIPT_DIR" \
            -DCMAKE_BUILD_TYPE=Release \
            -DCMAKE_CXX_FLAGS_RELEASE="-O3 -DNDEBUG" \
            -DDRAGONX_USE_SYSTEM_SDL3=OFF \
            -DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 \
            -DCMAKE_OSX_ARCHITECTURES="$MAC_ARCHS" \
            "${BDB_ARGS[@]}" \
            "${CMAKE_LITE_ARGS[@]}"
    fi

    info "Building with $JOBS jobs ..."
    cmake --build . -j "$JOBS"

    [[ -f "bin/${APP_BASENAME}" ]] || { err "macOS build failed"; exit 1; }

    # A full-node release MUST include the recovery helper. macOS needs a static libdb-6.2 (Homebrew
    # berkeley-db for a native build, or a vendored external/dragonx/depends/<triple>) — otherwise CMake
    # skips the target and this fails loudly rather than shipping a mac release with no recovery option.
    require_wallet_rebuild_helper "bin/dragonx-wallet-rebuild" "$(mac_bdb_dir "$MAC_ARCH")"

    # Strip — use osxcross strip for cross-builds
    if $IS_CROSS; then
        local STRIP_CMD="${OSXCROSS}/target/bin/${OSXCROSS_TRIPLE}-strip"
        if [[ -x "$STRIP_CMD" ]]; then
            info "Stripping (osxcross) ..."
            "$STRIP_CMD" "bin/${APP_BASENAME}"
        else
            warn "osxcross strip not found at $STRIP_CMD — skipping"
        fi
    else
        info "Stripping ..."
        strip "bin/${APP_BASENAME}"
        # Verify universal binary
        if command -v lipo &>/dev/null; then
            info "Architecture info:"
            lipo -info "bin/${APP_BASENAME}"
        fi
    fi
    info "Binary: $(du -h "bin/${APP_BASENAME}" | cut -f1)"

    # ── Create .app bundle ───────────────────────────────────────────────────
    mkdir -p "$out"
    # Clean only THIS variant's prior artifacts so full-node and lite releases can
    # coexist in release/mac/ (Linux/Windows scope their cleanup the same way). The
    # "ObsidianDragon-" glob never matches "ObsidianDragonLite-" (and vice versa),
    # and the ".app" names are exact.
    rm -rf "$out/${APP_BASENAME}.app" "$out/${APP_BASENAME}-"*.app.zip "$out/${APP_BASENAME}-"*.dmg

    local APP="$out/${APP_BASENAME}.app"
    local CONTENTS="$APP/Contents"
    local MACOS="$CONTENTS/MacOS"
    local RESOURCES="$CONTENTS/Resources"
    local FRAMEWORKS="$CONTENTS/Frameworks"

    mkdir -p "$MACOS" "$RESOURCES/res" "$FRAMEWORKS"

    # Main binary
    cp "bin/${APP_BASENAME}" "$MACOS/"
    chmod +x "$MACOS/${APP_BASENAME}"

    # Resources
    cp -r bin/res/* "$RESOURCES/res/" 2>/dev/null || true

    if should_bundle_full_node_assets; then
        # Daemon binaries (macOS native, from dragonxd-mac/)
        local daemon_dir="$SCRIPT_DIR/prebuilt-binaries/dragonxd-mac"
        if [[ -d "$daemon_dir" ]]; then
            for f in dragonxd dragonx-cli dragonx-tx; do
                [[ -f "$daemon_dir/$f" ]] && { cp "$daemon_dir/$f" "$MACOS/"; chmod +x "$MACOS/$f"; info "  Bundled $f"; }
            done
            for f in sapling-spend.params sapling-output.params; do
                [[ -f "$daemon_dir/$f" ]] && { cp "$daemon_dir/$f" "$MACOS/"; info "  Bundled $f"; }
            done
        elif ! $IS_CROSS; then
            # Native macOS: try standard paths
            local daemon_paths=(
                "$SCRIPT_DIR/../dragonxd"
                "$HOME/dragonx/src/dragonxd"
            )
            for p in "${daemon_paths[@]}"; do
                [[ -f "$p" ]] && { cp "$p" "$MACOS/dragonxd"; chmod +x "$MACOS/dragonxd"; info "  Bundled dragonxd"; break; }
            done
            local cli_paths=(
                "$SCRIPT_DIR/../dragonx-cli"
                "$HOME/dragonx/src/dragonx-cli"
            )
            for p in "${cli_paths[@]}"; do
                [[ -f "$p" ]] && { cp "$p" "$MACOS/dragonx-cli"; chmod +x "$MACOS/dragonx-cli"; info "  Bundled dragonx-cli"; break; }
            done
        else
            warn "prebuilt-binaries/dragonxd-mac/ not found — place macOS daemon binaries there for bundling"
        fi
        # Offline wallet-rebuild recovery helper — required (asserted after build); next to the daemon.
        cp "bin/dragonx-wallet-rebuild" "$MACOS/" && chmod +x "$MACOS/dragonx-wallet-rebuild" && info "  Bundled dragonx-wallet-rebuild"
    else
        info "Lite mode: skipping macOS daemon and Sapling/asmap bundling"
    fi

    # xmrig binary (from prebuilt-binaries/drg-xmrig/)
    local XMRIG_MAC="$SCRIPT_DIR/prebuilt-binaries/drg-xmrig/xmrig"
    if [[ -f "$XMRIG_MAC" ]]; then
        cp "$XMRIG_MAC" "$MACOS/xmrig"
        chmod +x "$MACOS/xmrig"
        info "  Bundled xmrig"
    else
        warn "xmrig not found — mining unavailable in .app"
    fi

    if should_bundle_full_node_assets; then
        # asmap.dat — placed in MacOS/ so the daemon finds it next to its binary
        find_asmap 2>/dev/null && {
            cp "$ASMAP_DAT" "$MACOS/asmap.dat"
            info "  Bundled asmap.dat"
        }
    fi

    # Bundle SDL3 dylib
    local sdl_dylib=""
    for candidate in \
        "$bd/_deps/sdl3-build/libSDL3.dylib" \
        "$bd/_deps/sdl3-build/libSDL3.0.dylib" \
        "/usr/local/lib/libSDL3.dylib" \
        "/opt/homebrew/lib/libSDL3.dylib"; do
        if [[ -f "$candidate" ]]; then
            sdl_dylib="$candidate"
            break
        fi
    done
    if [[ -n "$sdl_dylib" ]]; then
        cp "$sdl_dylib" "$FRAMEWORKS/"
        local sdl_name=$(basename "$sdl_dylib")
        # Fix the rpath so the binary finds SDL3 in Frameworks/
        if $IS_CROSS; then
            local INSTALL_NAME_TOOL="${OSXCROSS}/target/bin/${OSXCROSS_TRIPLE}-install_name_tool"
            [[ -x "$INSTALL_NAME_TOOL" ]] && "$INSTALL_NAME_TOOL" -change "@rpath/$sdl_name" "@executable_path/../Frameworks/$sdl_name" "$MACOS/${APP_BASENAME}" 2>/dev/null || true
        else
            install_name_tool -change "@rpath/$sdl_name" "@executable_path/../Frameworks/$sdl_name" "$MACOS/${APP_BASENAME}" 2>/dev/null || true
        fi
        info "  Bundled $sdl_name"
    fi

    # Launcher script (ensures working dir + dylib path). Uses ${APP_BASENAME} so the lite
    # variant (ObsidianDragonLite) gets a correctly-named launcher + .bin pair.
    mv "$MACOS/${APP_BASENAME}" "$MACOS/${APP_BASENAME}.bin"
    cat > "$MACOS/${APP_BASENAME}" <<LAUNCH
#!/bin/bash
DIR="\$(cd "\$(dirname "\$0")" && pwd)"
export DYLD_LIBRARY_PATH="\$DIR/../Frameworks:\$DYLD_LIBRARY_PATH"
export DRAGONX_RES_PATH="\$DIR/../Resources/res"
cd "\$DIR/../Resources"
exec "\$DIR/${APP_BASENAME}.bin" "\$@"
LAUNCH
    chmod +x "$MACOS/${APP_BASENAME}"

    # Info.plist — display name + bundle id differ per variant so lite and full-node .apps
    # can coexist; the executable matches the launcher (${APP_BASENAME}); the icon is shared.
    local APP_DISPLAY_NAME="DragonX Wallet"
    local APP_BUNDLE_ID="is.hush.dragonx"
    if $DO_LITE; then
        APP_DISPLAY_NAME="DragonX Wallet Lite"
        APP_BUNDLE_ID="is.hush.dragonx.lite"
    fi
    cat > "$CONTENTS/Info.plist" <<PLIST
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>CFBundleName</key>
    <string>${APP_DISPLAY_NAME}</string>
    <key>CFBundleDisplayName</key>
    <string>${APP_DISPLAY_NAME}</string>
    <key>CFBundleIdentifier</key>
    <string>${APP_BUNDLE_ID}</string>
    <key>CFBundleVersion</key>
    <string>${VERSION}</string>
    <key>CFBundleShortVersionString</key>
    <string>${VERSION}</string>
    <key>CFBundleExecutable</key>
    <string>${APP_BASENAME}</string>
    <key>CFBundleIconFile</key>
    <string>ObsidianDragon</string>
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleSignature</key>
    <string>DRGX</string>
    <key>LSMinimumSystemVersion</key>
    <string>11.0</string>
    <key>NSHighResolutionCapable</key>
    <true/>
    <key>NSSupportsAutomaticGraphicsSwitching</key>
    <true/>
    <key>LSApplicationCategoryType</key>
    <string>public.app-category.finance</string>
</dict>
</plist>
PLIST

    # ── Icon (.icns) ─────────────────────────────────────────────────────────
    if [[ -f "$SCRIPT_DIR/res/img/ObsidianDragon.icns" ]]; then
        cp "$SCRIPT_DIR/res/img/ObsidianDragon.icns" "$RESOURCES/ObsidianDragon.icns"
    elif [[ -f "$SCRIPT_DIR/res/icons/dragonx-256.png" ]]; then
        if command -v iconutil &>/dev/null && command -v sips &>/dev/null; then
            # Native macOS: sips + iconutil
            local iconset="$bd/ObsidianDragon.iconset"
            mkdir -p "$iconset"
            local src="$SCRIPT_DIR/res/icons/dragonx-256.png"
            for sz in 16 32 64 128 256; do
                sips -z $sz $sz "$src" --out "$iconset/icon_${sz}x${sz}.png" &>/dev/null || true
            done
            for sz in 16 32 128; do
                local r=$((sz * 2))
                sips -z $r $r "$src" --out "$iconset/icon_${sz}x${sz}@2x.png" &>/dev/null || true
            done
            iconutil -c icns "$iconset" -o "$RESOURCES/ObsidianDragon.icns" 2>/dev/null || true
            rm -rf "$iconset"
        elif command -v png2icns &>/dev/null; then
            # Linux: png2icns from icnsutils  (sudo apt install icnsutils)
            info "Creating .icns with png2icns ..."
            local src="$SCRIPT_DIR/res/icons/dragonx-256.png"
            # png2icns needs specific sizes; resize with ImageMagick if available
            if command -v convert &>/dev/null; then
                local icondir="$bd/icon-sizes"
                mkdir -p "$icondir"
                for sz in 16 32 48 128 256; do
                    convert "$src" -resize ${sz}x${sz} "$icondir/icon_${sz}.png" 2>/dev/null || true
                done
                png2icns "$RESOURCES/ObsidianDragon.icns" "$icondir"/icon_*.png 2>/dev/null || true
                rm -rf "$icondir"
            else
                # Just use the 256px as-is
                png2icns "$RESOURCES/ObsidianDragon.icns" "$src" 2>/dev/null || true
            fi
        else
            warn "No .icns tool found (install: sudo apt install icnsutils)"
        fi
    fi

    info ".app bundle created: $APP"

    # ── Zip the .app bundle ──────────────────────────────────────────────────
    local APP_ZIP="${APP_BASENAME}-${VERSION}-macOS-${MAC_ARCH}.app.zip"
    if command -v zip &>/dev/null; then
        (cd "$out" && zip -r "$APP_ZIP" "${APP_BASENAME}.app")
        info "App zip: $out/$APP_ZIP ($(du -h "$out/$APP_ZIP" | cut -f1))"
    fi

    # ── Create DMG ───────────────────────────────────────────────────────────
    # DMG filename matches the app bundle name (ObsidianDragon / ObsidianDragonLite).
    # The mounted volume + CFBundleName keep the "DragonX Wallet" display branding
    # (APP_DISPLAY_NAME above).
    local DMG_BASENAME="${APP_BASENAME}"
    local DMG_NAME="${DMG_BASENAME}-${VERSION}-macOS-${MAC_ARCH}.dmg"

    if command -v create-dmg &>/dev/null; then
        # create-dmg (works on macOS; also available on Linux via npm)
        info "Creating DMG with create-dmg ..."
        create-dmg \
            --volname "${APP_DISPLAY_NAME}" \
            --volicon "$RESOURCES/ObsidianDragon.icns" \
            --window-pos 200 120 \
            --window-size 600 400 \
            --icon-size 100 \
            --icon "${APP_BASENAME}.app" 150 190 \
            --app-drop-link 450 190 \
            --no-internet-enable \
            "$out/$DMG_NAME" \
            "$APP" 2>/dev/null && {
            info "DMG: $out/$DMG_NAME ($(du -h "$out/$DMG_NAME" | cut -f1))"
        } || warn "create-dmg failed — .app bundle still available"
    elif command -v hdiutil &>/dev/null; then
        # Native macOS
        info "Creating DMG with hdiutil ..."
        local staging="$bd/dmg-staging"
        rm -rf "$staging"
        mkdir -p "$staging"
        cp -a "$APP" "$staging/"
        ln -s /Applications "$staging/Applications"
        hdiutil create -volname "${APP_DISPLAY_NAME}" \
            -srcfolder "$staging" \
            -ov -format UDZO \
            "$out/$DMG_NAME" 2>/dev/null && {
            info "DMG: $out/$DMG_NAME ($(du -h "$out/$DMG_NAME" | cut -f1))"
        } || warn "hdiutil failed — .app bundle still available"
        rm -rf "$staging"
    elif command -v genisoimage &>/dev/null; then
        # Linux fallback: genisoimage produces a hybrid ISO/DMG that macOS can open
        info "Creating DMG with genisoimage (Linux) ..."
        local staging="$bd/dmg-staging"
        rm -rf "$staging"
        mkdir -p "$staging"
        cp -a "$APP" "$staging/"
        # Can't create a real symlink to /Applications in an ISO, but the .app
        # is the important part — users drag it to Applications manually.
        genisoimage -V "${APP_DISPLAY_NAME}" \
            -D -R -apple -no-pad \
            -o "$out/$DMG_NAME" \
            "$staging" 2>/dev/null && {
            info "DMG: $out/$DMG_NAME ($(du -h "$out/$DMG_NAME" | cut -f1))"
            info "  (ISO/HFS hybrid — mountable on macOS)"
        } || warn "genisoimage failed — .app bundle still available"
        rm -rf "$staging"
    else
        warn "No DMG tool found — .app bundle still available"
        echo ""
        echo "  To create DMGs on Linux, install one of:"
        echo "    sudo apt install genisoimage          # Basic DMG (recommended)"
        echo "    sudo apt install icnsutils             # For .icns icon creation"
        echo ""
    fi

    info "macOS release artifacts: $out/"
    ls -lhR "$out/" 2>/dev/null | head -30
}

# ═══════════════════════════════════════════════════════════════════════════════
#  MAIN
# ═══════════════════════════════════════════════════════════════════════════════
echo -e "${GREEN}DragonX Wallet v${VERSION} — Unified Build${NC}"
echo "─────────────────────────────────────────"

$DO_DEV   && build_dev
$DO_LINUX && build_release_linux
$DO_WIN   && build_release_win
$DO_MAC   && build_release_mac

header "Done"
if $DO_LINUX || $DO_WIN || $DO_MAC; then
    echo -e "  Release artifacts in: ${GREEN}$SCRIPT_DIR/release/${NC}"
    [[ -d "$SCRIPT_DIR/release/linux" ]]   && echo -e "    ${CYAN}linux/${NC}     — AppImage + binary"
    [[ -d "$SCRIPT_DIR/release/windows" ]] && echo -e "    ${CYAN}windows/${NC}   — .exe + .zip"
    [[ -d "$SCRIPT_DIR/release/mac" ]]     && echo -e "    ${CYAN}mac/${NC}       — .app + .dmg"
fi

# Reaching here means the build completed (real failures exit 1 at their point of failure).
# Exit 0 explicitly: the final `[[ -d release/mac ]] && echo` above returns non-zero on a
# non-mac build — and since set -e exempts the left side of an &&, that status would otherwise
# become the script's exit code and make a successful build report failure (e.g. to CI).
exit 0
