#!/bin/bash
# HOT-Step CPP — macOS Release Packager
#
# Builds a portable .tar.gz release that requires ZERO prerequisites.
# End users just extract and run ./launch.sh — no Xcode, no brew, no npm.
#
# What gets bundled:
#   - Pre-built C++ engine binaries (Metal GPU)
#   - Bundled Node.js 22 runtime (~40 MB)
#   - Pre-installed server dependencies (production only)
#   - Pre-built React UI
#   - Launch scripts
#
# Usage:
#   ./package-release.sh                # Build release package
#   ./package-release.sh --skip-build   # Package only (engine already built)
#
# Output:
#   HOT-Step-CPP-v{VERSION}-macOS-arm64.tar.gz

set -e

cd "$(dirname "$0")"
ROOT_DIR="$(pwd)"

# ── Config ───────────────────────────────────────────────────────────
VERSION="${VERSION:-1.0.0}"
ARCH="arm64"
NODE_VERSION="22.22.2"
NODE_URL="https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-darwin-${ARCH}.tar.xz"
RELEASE_NAME="HOT-Step-CPP-v${VERSION}-macOS-${ARCH}"
SKIP_BUILD=0

for arg in "$@"; do
    case $arg in
        --skip-build) SKIP_BUILD=1 ;;
        --version=*) VERSION="${arg#*=}"; RELEASE_NAME="HOT-Step-CPP-v${VERSION}-macOS-${ARCH}" ;;
    esac
done

STAGING_DIR="${ROOT_DIR}/release-staging/${RELEASE_NAME}"
OUTPUT_FILE="${ROOT_DIR}/${RELEASE_NAME}.tar.gz"

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

step() { echo -e "\n${CYAN}${BOLD}[$1/$TOTAL_STEPS]${NC} ${BOLD}$2${NC}"; }
ok()   { echo -e "  ${GREEN}✓${NC} $1"; }
warn() { echo -e "  ${YELLOW}⚠${NC} $1"; }
fail() { echo -e "  ${RED}✗ $1${NC}"; exit 1; }

TOTAL_STEPS=7

echo ""
echo -e "${BOLD}╔══════════════════════════════════════════╗${NC}"
echo -e "${BOLD}║   HOT-Step CPP — Release Packager        ║${NC}"
echo -e "${BOLD}║      macOS ${ARCH} Portable Build       ║${NC}"
echo -e "${BOLD}╚══════════════════════════════════════════╝${NC}"
echo ""
echo "  Version:  ${VERSION}"
echo "  Output:   ${RELEASE_NAME}.tar.gz"
echo ""

# ── Find compatible Node for the build process ──────────────────────
find_node() {
    for bp in "/opt/homebrew/opt/node@22/bin" "/usr/local/opt/node@22/bin"; do
        if [ -x "${bp}/node" ]; then echo "$bp"; return 0; fi
    done
    if command -v node &>/dev/null; then
        local ver; ver="$(node --version 2>/dev/null | tr -d 'v')"
        local major="${ver%%.*}"
        if [ "$major" -ge 18 ] && [ "$major" -lt 24 ] 2>/dev/null; then
            echo "$(dirname "$(command -v node)")"; return 0
        fi
    fi
    return 1
}

NODE_DIR=""
if NODE_DIR=$(find_node); then
    export PATH="${NODE_DIR}:${PATH}"
else
    fail "Node.js 18-22 required for building. Run: brew install node@22"
fi

# ── Step 1: Build engine ─────────────────────────────────────────────
step 1 "Building C++ engine..."

if [ "$SKIP_BUILD" -eq 1 ] && [ -f "engine/build/ace-server" ]; then
    ok "Skipping build (--skip-build, binaries exist)"
else
    cd engine
    rm -rf build && mkdir build && cd build
    cmake .. \
        -DGGML_METAL=ON \
        -DGGML_METAL_EMBED_LIBRARY=ON \
        -DGGML_BACKEND_DL=OFF \
        -DCMAKE_BUILD_TYPE=Release \
        2>&1 | tail -3
    NCPU=$(sysctl -n hw.ncpu 2>/dev/null || echo 4)
    cmake --build . --config Release -j "${NCPU}" 2>&1 | grep -E '^\[100|Built target|error:'
    cd "${ROOT_DIR}"
fi

# Verify binaries
for bin in ace-server mastering mp3-codec vst-host; do
    [ -f "engine/build/${bin}" ] || fail "engine/build/${bin} not found"
done
ok "All engine binaries built"

# Fix rpaths: replace any absolute build-dir rpaths with @executable_path
# so dylibs are found relative to the binary (portable release requirement).
echo "  Fixing dylib rpaths for portability..."
for bin in engine/build/ace-server engine/build/ace-lm engine/build/ace-synth \
           engine/build/ace-understand engine/build/neural-codec engine/build/quantize; do
    if [ -f "$bin" ]; then
        # Remove all existing rpaths (may be absolute build paths)
        for old_rpath in $(otool -l "$bin" 2>/dev/null | grep -A1 LC_RPATH | grep "path " | awk '{print $2}'); do
            install_name_tool -delete_rpath "$old_rpath" "$bin" 2>/dev/null || true
        done
        # Add the portable @executable_path rpath
        install_name_tool -add_rpath "@executable_path" "$bin" 2>/dev/null || true
    fi
done
ok "Rpaths fixed (@executable_path)"

# ── Step 2: Install server deps (production) ─────────────────────────
step 2 "Installing server dependencies (production)..."

cd server
rm -rf node_modules
npm install --omit=dev --loglevel=warn 2>&1 | tail -3
# Rebuild native modules
npm rebuild better-sqlite3 2>&1 | tail -1
# tsx is in devDependencies but needed at runtime to run the TypeScript server
npm install tsx --save-optional --loglevel=warn 2>&1 | tail -1
ok "Server dependencies installed (production + tsx)"
cd "${ROOT_DIR}"

# ── Step 3: Build UI ─────────────────────────────────────────────────
step 3 "Building production UI..."

cd ui
[ -d "node_modules" ] || npm install --loglevel=warn
npx vite build 2>&1 | tail -3
ok "UI built"
cd "${ROOT_DIR}"

# ── Step 4: Download Node.js runtime ─────────────────────────────────
step 4 "Downloading Node.js ${NODE_VERSION} runtime..."

NODE_CACHE="${ROOT_DIR}/release-staging/node-cache"
NODE_ARCHIVE="${NODE_CACHE}/node-v${NODE_VERSION}-darwin-${ARCH}.tar.xz"
NODE_EXTRACTED="${NODE_CACHE}/node-v${NODE_VERSION}-darwin-${ARCH}"

mkdir -p "${NODE_CACHE}"

if [ -f "${NODE_EXTRACTED}/bin/node" ]; then
    ok "Using cached Node.js download"
else
    echo "  Downloading from ${NODE_URL}..."
    curl -L --progress-bar -o "${NODE_ARCHIVE}" "${NODE_URL}"
    cd "${NODE_CACHE}"
    tar xf "$(basename "${NODE_ARCHIVE}")"
    cd "${ROOT_DIR}"
    ok "Node.js ${NODE_VERSION} downloaded"
fi

# ── Step 5: Assemble release ─────────────────────────────────────────
step 5 "Assembling release package..."

rm -rf "${STAGING_DIR}"
mkdir -p "${STAGING_DIR}"

# Engine binaries + dylibs
mkdir -p "${STAGING_DIR}/engine/build"
for bin in ace-server ace-lm ace-synth ace-understand mastering mp3-codec neural-codec quantize vst-host; do
    [ -f "engine/build/${bin}" ] && cp "engine/build/${bin}" "${STAGING_DIR}/engine/build/"
done
# Copy ggml dylibs
cp engine/build/libggml*.dylib "${STAGING_DIR}/engine/build/" 2>/dev/null || true

# Lua plugins — dual directory system:
#   engine/plugins/  = native/built-in plugins (solvers, schedulers, guidance)
#   plugins/         = community/user plugins (same structure, overrides native)
# ace-server scans both via engine_dir and project_dir resolution from binary path
if [ -d "engine/plugins" ]; then
    mkdir -p "${STAGING_DIR}/engine/plugins"
    cp -r engine/plugins/* "${STAGING_DIR}/engine/plugins/"
    PLUGIN_COUNT=$(find "${STAGING_DIR}/engine/plugins" -name "*.lua" | wc -l | tr -d ' ')
    ok "${PLUGIN_COUNT} native plugins copied (engine/plugins/)"
fi
if [ -d "plugins" ]; then
    cp -r plugins "${STAGING_DIR}/"
    ok "Community plugins dir copied (plugins/)"
fi

# Server (source + node_modules, no devDependencies)
mkdir -p "${STAGING_DIR}/server"
cp server/package.json server/package-lock.json "${STAGING_DIR}/server/"
cp server/tsconfig.json "${STAGING_DIR}/server/"
cp -r server/node_modules "${STAGING_DIR}/server/"
cp -r server/src "${STAGING_DIR}/server/"
[ -f server/migrate_lireek.cjs ] && cp server/migrate_lireek.cjs "${STAGING_DIR}/server/"
ok "Server copied"

# Pre-built UI
mkdir -p "${STAGING_DIR}/ui"
cp -r ui/dist "${STAGING_DIR}/ui/"
ok "UI copied"

# Node.js runtime (just the binary + npm essentials)
mkdir -p "${STAGING_DIR}/runtime"
cp "${NODE_EXTRACTED}/bin/node" "${STAGING_DIR}/runtime/node"
# Copy npm/npx for server to use tsx
mkdir -p "${STAGING_DIR}/runtime/lib"
cp -r "${NODE_EXTRACTED}/lib/node_modules" "${STAGING_DIR}/runtime/lib/"
# Create npx/npm symlinks
ln -sf "../lib/node_modules/npm/bin/npm-cli.js" "${STAGING_DIR}/runtime/npm"
ln -sf "../lib/node_modules/npm/bin/npx-cli.js" "${STAGING_DIR}/runtime/npx"
ok "Node.js runtime bundled"

# Empty directories for user content
mkdir -p "${STAGING_DIR}/models"
mkdir -p "${STAGING_DIR}/adapters"

# Scripts
cp launch.sh "${STAGING_DIR}/"
cp .env.example "${STAGING_DIR}/"
chmod +x "${STAGING_DIR}/launch.sh"

# README snippet for release
cat > "${STAGING_DIR}/README.txt" << 'HEREDOC'
HOT-Step CPP — macOS (Apple Silicon)

Quick Start:
  1. Extract this archive
  2. Run: ./launch.sh
  3. Open http://localhost:3001 in your browser
  4. Go to Models → Get More Models to download AI models (~7 GB)

If macOS blocks the app (unsigned binary):
  xattr -cr /path/to/HOT-Step-CPP-macOS-arm64/

Requirements:
  - macOS 13+ (Apple Silicon)
  - ~10 GB free disk space
  - No other software needed — Node.js is bundled

For the full README, source code, and updates:
  https://github.com/scragnog/HOT-Step-CPP
HEREDOC
ok "Release assembled"

# ── Step 6: Set permissions ──────────────────────────────────────────
step 6 "Setting permissions..."

find "${STAGING_DIR}/engine/build" -type f -perm +0111 -exec chmod +x {} \; 2>/dev/null || true
chmod +x "${STAGING_DIR}/engine/build/"* 2>/dev/null || true
chmod +x "${STAGING_DIR}/runtime/node"
# npm/npx are symlinks to .js files — make targets executable
chmod +x "${STAGING_DIR}/runtime/lib/node_modules/npm/bin/npm-cli.js" 2>/dev/null || true
chmod +x "${STAGING_DIR}/runtime/lib/node_modules/npm/bin/npx-cli.js" 2>/dev/null || true
chmod +x "${STAGING_DIR}/launch.sh"
ok "Permissions set"

# ── Step 7: Create archive ──────────────────────────────────────────
step 7 "Creating archive..."

cd release-staging
tar czf "${OUTPUT_FILE}" "${RELEASE_NAME}"
cd "${ROOT_DIR}"

# Size info
SIZE_MB=$(du -sm "${OUTPUT_FILE}" | cut -f1)
ok "${RELEASE_NAME}.tar.gz (${SIZE_MB} MB)"

# ── Done ─────────────────────────────────────────────────────────────
echo ""
echo -e "${GREEN}${BOLD}╔══════════════════════════════════════════╗${NC}"
echo -e "${GREEN}${BOLD}║        Release package ready! 🎉         ║${NC}"
echo -e "${GREEN}${BOLD}╚══════════════════════════════════════════╝${NC}"
echo ""
echo "  File:  ${OUTPUT_FILE}"
echo "  Size:  ${SIZE_MB} MB"
echo ""
echo "  Test locally:"
echo "    cd release-staging/${RELEASE_NAME}"
echo "    ./launch.sh"
echo ""

# Clean up staging (keep the archive)
# rm -rf release-staging
