#!/bin/bash
# Cross-compile orquesta-agent to standalone binaries for all supported platforms,
# regenerate the public/agent/manifest.json, and copy artefacts into place.
#
# Requires `bun` on PATH.
set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT="$(dirname "$SCRIPT_DIR")"
BIN_DIR="$ROOT/bin"
PUBLIC_DIR="$ROOT/../../public/agent"
VERSION="$(node -p "require('$ROOT/package.json').version")"

if ! command -v bun >/dev/null 2>&1; then
  echo "error: bun not found. Install with: curl -fsSL https://bun.sh/install | bash" >&2
  exit 1
fi

# rcodesign (Rust apple-codesign) re-signs the macOS binaries with a VALID ad-hoc
# signature. This is NOT optional: bun cross-compiles the darwin targets on this
# Linux host, signs the Mach-O, then APPENDS its embedded payload WITHOUT
# re-signing — so the code-signature page hashes for the payload region are
# wrong. Linux ignores signatures, but Apple Silicon validates them per-page and
# SIGKILLs the process the instant it maps an invalid page → the agent dies
# silently on exec ("Killed: 9") and never connects. rcodesign recomputes the
# whole signature over the final file, fixing it. Install: cargo install
# apple-codesign  (or grab a release binary from indygreg/apple-platform-rs).
if ! command -v rcodesign >/dev/null 2>&1; then
  echo "error: rcodesign not found — macOS binaries would ship with a broken" >&2
  echo "       ad-hoc signature and get SIGKILLed on Apple Silicon." >&2
  echo "       Install with: cargo install apple-codesign" >&2
  exit 1
fi

# Apply a valid ad-hoc signature to a macOS Mach-O binary (in place). bun's
# embedded signature is malformed (see note above) and rcodesign refuses to
# parse it, so strip it first, then sign from a clean slate.
adhoc_sign_macos() {
  echo "    re-signing (ad-hoc) $(basename "$1")"
  python3 "$SCRIPT_DIR/strip-macho-sig.py" "$1" >/dev/null
  rcodesign sign "$1" >/dev/null
}

mkdir -p "$BIN_DIR" "$PUBLIC_DIR"

echo "Building orquesta-agent v${VERSION} binaries..."

# Targets: <bun-target>:<output-suffix>
# win32-x64 closes the bare-Windows install gap (served via /install.ps1).
TARGETS=(
  "bun-linux-x64:linux-x64"
  "bun-linux-arm64:linux-arm64"
  "bun-darwin-x64:macos-x64"
  "bun-darwin-arm64:macos-arm64"
  "bun-windows-x64:win32-x64"
)

for entry in "${TARGETS[@]}"; do
  bun_target="${entry%%:*}"
  suffix="${entry##*:}"
  # Windows binaries need a .exe extension (bun emits one; /install.ps1 + the
  # manifest reference the .exe name).
  ext=""
  case "$suffix" in win32-*) ext=".exe" ;; esac
  out="$BIN_DIR/orquesta-agent-${suffix}${ext}"
  echo "  → ${suffix}"
  bun build "$ROOT/dist/index.js" \
    --compile \
    --target="$bun_target" \
    --outfile="$out" \
    --external @homebridge/node-pty-prebuilt-multiarch \
    --define="process.env.ORQUESTA_AGENT_VERSION=\"${VERSION}\"" >/dev/null
  # Fix bun's broken darwin signature BEFORE copying/hashing (see note above).
  case "$suffix" in macos-*) adhoc_sign_macos "$out" ;; esac
  cp "$out" "$PUBLIC_DIR/"
done

# JS bundle (for Node fallback when binary is unavailable)
echo "  → bundle (orquesta-agent.mjs)"
npx --no esbuild "$ROOT/dist/index.js" \
  --bundle \
  --platform=node \
  --target=node20 \
  --format=esm \
  --outfile="$BIN_DIR/orquesta-agent.mjs" \
  --external:@homebridge/node-pty-prebuilt-multiarch \
  --banner:js="import{createRequire}from'module';const require=createRequire(import.meta.url);" \
  --define:process.env.ORQUESTA_AGENT_VERSION="\"${VERSION}\"" \
  --minify >/dev/null
cp "$BIN_DIR/orquesta-agent.mjs" "$PUBLIC_DIR/"

# ── Prebuilt node-pty binding (for the no-Node /install path) ──────────────────
# Interactive sessions need @homebridge/node-pty-prebuilt-multiarch, which the
# standalone binary CANNOT embed (it's --external). The /install script provisions
# it at install time from this tarball, so a box with NO Node/npm still gets
# working interactive sessions — the agent's runtime npm self-heal is only a
# fallback (and can't run on a box without npm, which is exactly the no-Node case
# standalone binaries exist for). The tarball is tiny (~2.6MB, all platforms; bun
# loads the matching prebuild by its own N-API ABI at runtime). Keep
# NODE_PTY_VERSION in sync with src/executor.ts.
NODE_PTY_PKG="@homebridge/node-pty-prebuilt-multiarch"
NODE_PTY_VERSION="0.13.1"
NODE_PTY_TARBALL="node-pty-prebuilt-multiarch-${NODE_PTY_VERSION}.tgz"
echo "  → ${NODE_PTY_TARBALL}"
NPT_TMP="$(mktemp -d)"
npm install "${NODE_PTY_PKG}@${NODE_PTY_VERSION}" --no-save --no-audit --no-fund --loglevel=error --prefix "$NPT_TMP" >/dev/null 2>&1
# Tar with the package dir at the archive root so it extracts straight into
# <cache>/node_modules/@homebridge/ (matching nativeCacheEntry() in executor.ts).
tar -czf "$PUBLIC_DIR/$NODE_PTY_TARBALL" -C "$NPT_TMP/node_modules/@homebridge" node-pty-prebuilt-multiarch
rm -rf "$NPT_TMP"

# Windows can't reuse the tarball above: node-pty loads conpty.node/pty.node from
# build/Release (not prebuilds/), and a Linux `npm install` never fetches the win32
# binding. So the win32 tarball is built OUT-OF-BAND on a real Windows host (the
# dockur harness: `npm install` the package, then tar the installed dir incl.
# build/Release) and committed to git. We only reference it here; if it's missing
# (e.g. a clean clone before the artifact exists) we omit the manifest entry rather
# than fail the Linux build, and install.ps1 falls back to runtime npm self-heal.
NODE_PTY_TARBALL_WIN32="node-pty-prebuilt-multiarch-${NODE_PTY_VERSION}-win32-x64.tgz"
# Same story for macOS x64: built out-of-band on a real Mac (node-gyp produces
# build/Release/pty.node, a Mach-O bundle; inert linux prebuilds stripped) and
# committed to git. macOS arm64 needs an Apple Silicon host to build+verify — not
# shipped until one is available; arm64 falls back to runtime npm self-heal.
NODE_PTY_TARBALL_MACOS_X64="node-pty-prebuilt-multiarch-${NODE_PTY_VERSION}-darwin-x64.tgz"

# Regenerate manifest.json
echo "  → manifest.json"
GIT_COMMIT="$(git -C "$ROOT" rev-parse HEAD 2>/dev/null || echo unknown)"
BUILD_DATE="$(date -u +%Y-%m-%dT%H:%M:%SZ)"

hash_of() { sha256sum "$1" | cut -d' ' -f1; }
size_of() { stat -c%s "$1" 2>/dev/null || stat -f%z "$1"; }

# Build the win32 node-pty manifest entry from the committed prebuilt tarball (see
# the OUT-OF-BAND note above). Omitted if the artifact is absent.
NPT_WIN32_JSON=""
if [ -f "$PUBLIC_DIR/$NODE_PTY_TARBALL_WIN32" ]; then
  echo "  → ${NODE_PTY_TARBALL_WIN32} (prebuilt, win32)"
  NPT_WIN32_JSON="$(printf '  "nodePtyWin32": {\n    "file": "%s",\n    "version": "%s",\n    "sha256": "%s",\n    "size": %s\n  },' \
    "$NODE_PTY_TARBALL_WIN32" "$NODE_PTY_VERSION" \
    "$(hash_of "$PUBLIC_DIR/$NODE_PTY_TARBALL_WIN32")" "$(size_of "$PUBLIC_DIR/$NODE_PTY_TARBALL_WIN32")")"
else
  echo "  → ${NODE_PTY_TARBALL_WIN32} MISSING — omitting nodePtyWin32 (win32 no-Node sessions self-heal via npm)"
fi

# macOS x64 node-pty manifest entry from the committed prebuilt tarball. Omitted if absent.
NPT_MACOS_X64_JSON=""
if [ -f "$PUBLIC_DIR/$NODE_PTY_TARBALL_MACOS_X64" ]; then
  echo "  → ${NODE_PTY_TARBALL_MACOS_X64} (prebuilt, darwin-x64)"
  NPT_MACOS_X64_JSON="$(printf '  "nodePtyMacosX64": {\n    "file": "%s",\n    "version": "%s",\n    "sha256": "%s",\n    "size": %s\n  },' \
    "$NODE_PTY_TARBALL_MACOS_X64" "$NODE_PTY_VERSION" \
    "$(hash_of "$PUBLIC_DIR/$NODE_PTY_TARBALL_MACOS_X64")" "$(size_of "$PUBLIC_DIR/$NODE_PTY_TARBALL_MACOS_X64")")"
else
  echo "  → ${NODE_PTY_TARBALL_MACOS_X64} MISSING — omitting nodePtyMacosX64 (macOS-x64 no-Node sessions self-heal via npm)"
fi

cat > "$PUBLIC_DIR/manifest.json" <<EOF
{
  "version": "${VERSION}",
  "buildDate": "${BUILD_DATE}",
  "gitCommit": "${GIT_COMMIT}",
  "binaries": {
    "linux-x64": {
      "file": "orquesta-agent-linux-x64",
      "sha256": "$(hash_of "$PUBLIC_DIR/orquesta-agent-linux-x64")",
      "size": $(size_of "$PUBLIC_DIR/orquesta-agent-linux-x64")
    },
    "linux-arm64": {
      "file": "orquesta-agent-linux-arm64",
      "sha256": "$(hash_of "$PUBLIC_DIR/orquesta-agent-linux-arm64")",
      "size": $(size_of "$PUBLIC_DIR/orquesta-agent-linux-arm64")
    },
    "macos-x64": {
      "file": "orquesta-agent-macos-x64",
      "sha256": "$(hash_of "$PUBLIC_DIR/orquesta-agent-macos-x64")",
      "size": $(size_of "$PUBLIC_DIR/orquesta-agent-macos-x64")
    },
    "macos-arm64": {
      "file": "orquesta-agent-macos-arm64",
      "sha256": "$(hash_of "$PUBLIC_DIR/orquesta-agent-macos-arm64")",
      "size": $(size_of "$PUBLIC_DIR/orquesta-agent-macos-arm64")
    },
    "win32-x64": {
      "file": "orquesta-agent-win32-x64.exe",
      "sha256": "$(hash_of "$PUBLIC_DIR/orquesta-agent-win32-x64.exe")",
      "size": $(size_of "$PUBLIC_DIR/orquesta-agent-win32-x64.exe")
    }
  },
  "bundle": {
    "file": "orquesta-agent.mjs",
    "sha256": "$(hash_of "$PUBLIC_DIR/orquesta-agent.mjs")",
    "size": $(size_of "$PUBLIC_DIR/orquesta-agent.mjs")
  },
  "nodePty": {
    "file": "${NODE_PTY_TARBALL}",
    "version": "${NODE_PTY_VERSION}",
    "sha256": "$(hash_of "$PUBLIC_DIR/$NODE_PTY_TARBALL")",
    "size": $(size_of "$PUBLIC_DIR/$NODE_PTY_TARBALL")
  },
${NPT_WIN32_JSON}
${NPT_MACOS_X64_JSON}
  "npm": {
    "package": "orquesta-agent@${VERSION}"
  }
}
EOF

# ── Sign the manifest (closed-source provenance) ──────────────────────────────
# The install script ships a PINNED Orquesta public key and refuses to run a
# binary whose manifest signature doesn't verify — so a compromised file origin
# (getorquesta.com) cannot serve a tampered binary, even with a matching SHA.
# Signs only when the private key is provided (path in $ORQUESTA_MANIFEST_SIGNING_KEY),
# so a normal dev build without the key still works (just unsigned).
if [ -n "${ORQUESTA_MANIFEST_SIGNING_KEY:-}" ] && [ -f "$ORQUESTA_MANIFEST_SIGNING_KEY" ]; then
  echo "  → manifest.json.sig (signing with ORQUESTA_MANIFEST_SIGNING_KEY)"
  openssl dgst -sha256 -sign "$ORQUESTA_MANIFEST_SIGNING_KEY" -out "$PUBLIC_DIR/manifest.sig.bin" "$PUBLIC_DIR/manifest.json"
  base64 -w0 "$PUBLIC_DIR/manifest.sig.bin" > "$PUBLIC_DIR/manifest.json.sig" 2>/dev/null \
    || base64 "$PUBLIC_DIR/manifest.sig.bin" | tr -d '\n' > "$PUBLIC_DIR/manifest.json.sig"
  rm -f "$PUBLIC_DIR/manifest.sig.bin"
else
  echo "  → manifest.json.sig SKIPPED (no ORQUESTA_MANIFEST_SIGNING_KEY) — manifest will be UNSIGNED"
fi

echo ""
echo "Done. Artifacts in $PUBLIC_DIR:"
ls -lh "$PUBLIC_DIR"
