#!/usr/bin/env bash
# Build a self-contained backend release from an exact Git revision and publish it.
set -euo pipefail

DEPLOY="${OVERDECK_DEPLOY_DIR:-$HOME/.local/share/overdeck/deploy}"
BACKEND_ROOT="${OVERDECK_BACKEND_RELEASE_ROOT:-${XDG_STATE_HOME:-$HOME/.local/state}/overdeck/backend}"
RELEASE_TOOL="${OVERDECK_BACKEND_RELEASE_TOOL:-$DEPLOY/packaging/backend-release.sh}"

usage() {
  printf 'usage: %s <collector|controller|botmaster-proxy|actions-gateway> <sha>\n' "$0" >&2
  exit 64
}

(($# == 2)) || usage
component=$1
sha=$2
case "$component" in
  collector) package_name=overdeck-collector ;;
  controller) package_name=overdeck-controller ;;
  botmaster-proxy|actions-gateway) package_name= ;;
  *) usage ;;
esac
[[ "$sha" =~ ^[0-9a-f]{40}$ ]] || { printf 'stage-backend-release: invalid full sha\n' >&2; exit 64; }
[[ -x "$RELEASE_TOOL" ]] || { printf 'stage-backend-release: release tool unavailable\n' >&2; exit 65; }
git -C "$DEPLOY" cat-file -e "${sha}^{commit}" 2>/dev/null \
  || { printf 'stage-backend-release: revision is unavailable: %s\n' "$sha" >&2; exit 65; }

release="$BACKEND_ROOT/$component/releases/$sha"
if [[ -e "$release" || -L "$release" ]]; then
  OVERDECK_BACKEND_RELEASE_ROOT="$BACKEND_ROOT" "$RELEASE_TOOL" validate "$component" "$sha" \
    || { printf 'stage-backend-release: existing release is invalid\n' >&2; exit 65; }
  printf '%s\n' "$release"
  exit 0
fi

build_root="$BACKEND_ROOT/$component/.build"
staging_root="$BACKEND_ROOT/$component/.staging"
mkdir -p "$build_root" "$staging_root"
build=$(mktemp -d "$build_root/$sha.XXXXXX")
stage=$(mktemp -d "$staging_root/$sha.XXXXXX")
log="$build/build.log"
cleanup() {
  rm -rf -- "$build"
  if [[ -d "$stage" ]]; then
    chmod -R u+w "$stage" 2>/dev/null || true
    rm -rf -- "$stage"
  fi
}
trap cleanup EXIT

# Archive the named commit instead of trusting whichever revision happens to be checked out.
case "$component" in
  collector)
    archive_paths=(package.json pnpm-lock.yaml pnpm-workspace.yaml collector packages/activity-contract packages/report-contract modules/workstation/claude/hooks/lib/decision-marker.mjs)
    ;;
  controller)
    archive_paths=(package.json pnpm-lock.yaml pnpm-workspace.yaml controller modules/workstation/claude/workflows/lib modules/workstation/claude/workflows/hooks)
    ;;
  botmaster-proxy)
    archive_paths=(packaging/botmaster-proxy.ts modules/botmaster/notify)
    ;;
  actions-gateway)
    archive_paths=(tsconfig.json modules/actions-gateway)
    ;;
esac
git -C "$DEPLOY" archive "$sha" -- "${archive_paths[@]}" | tar -x -C "$build"

case "$component" in
  collector|controller)
    # The workspace dependency is included so pnpm can make a contained production snapshot.
    if ! LOCAL_GATE_ACTIVE=1 pnpm --dir "$build" --filter "$package_name" deploy --legacy --prod --prefer-offline "$stage/$component" \
      >"$log" 2>&1; then
      printf 'stage-backend-release: dependency snapshot failed: %s\n' \
        "$(tail -c 500 "$log" | tr '\n\"' ' _')" >&2
      exit 1
    fi
    ;;
  botmaster-proxy)
    mkdir -p "$stage/packaging" "$stage/modules/botmaster"
    cp --preserve=mode,timestamps -- "$build/packaging/botmaster-proxy.ts" "$stage/packaging/"
    cp -a -- "$build/modules/botmaster/notify" "$stage/modules/botmaster/"
    ;;
  actions-gateway)
    node_bin="${OVERDECK_BUILD_NODE_BIN:-/usr/bin/node}"
    tsc_js="${OVERDECK_TSC_JS:-}"
    if [[ -z "$tsc_js" ]]; then
      tsc_js=$(printf '%s\n' "$DEPLOY"/node_modules/.pnpm/typescript@*/node_modules/typescript/bin/tsc | head -1)
    fi
    node_type_root="${OVERDECK_NODE_TYPE_ROOT:-}"
    if [[ -z "$node_type_root" ]]; then
      node_type_root=$(printf '%s\n' "$DEPLOY"/node_modules/.pnpm/@types+node@*/node_modules/@types | head -1)
    fi
    [[ -x "$node_bin" && -f "$tsc_js" && -d "$node_type_root/node" ]] \
      || { printf 'stage-backend-release: TypeScript compiler unavailable\n' >&2; exit 69; }
    mkdir -p "$stage/actions-gateway/dist"
    cat >"$build/modules/actions-gateway/tsconfig.release.json" <<JSON
{"extends":"./tsconfig.json","compilerOptions":{"noEmit":false,"outDir":"$stage/actions-gateway/dist","rootDir":".","types":["node"],"typeRoots":["$node_type_root"]},"include":["src/**/*.ts"]}
JSON
    if ! "$node_bin" "$tsc_js" --project "$build/modules/actions-gateway/tsconfig.release.json" >"$log" 2>&1; then
      printf 'stage-backend-release: Actions Gateway build failed: %s\n' \
        "$(tail -c 500 "$log" | tr '\n\"' ' _')" >&2
      exit 1
    fi
    cp --preserve=mode,timestamps -- "$build/modules/actions-gateway/package.json" "$stage/actions-gateway/package.json"
    ;;
esac

if [[ "$component" == collector ]]; then
  marker_path=modules/workstation/claude/hooks/lib/decision-marker.mjs
  mkdir -p "$stage/$(dirname "$marker_path")"
  cp --preserve=mode,timestamps -- "$build/$marker_path" "$stage/$marker_path"
elif [[ "$component" == controller ]]; then
  mkdir -p "$stage/modules/workstation/claude/workflows"
  cp -a "$build/modules/workstation/claude/workflows/lib" \
    "$build/modules/workstation/claude/workflows/hooks" \
    "$stage/modules/workstation/claude/workflows/"
  deployed_tree=$(git -C "$DEPLOY" rev-parse "${sha}^{tree}") \
    || { printf 'stage-backend-release: cannot resolve controller tree\n' >&2; exit 65; }
  artifact_digest=$(git -C "$DEPLOY" archive "$sha" -- controller | sha256sum | cut -d' ' -f1) \
    || { printf 'stage-backend-release: cannot digest controller artifact\n' >&2; exit 65; }
  [[ "$deployed_tree" =~ ^[0-9a-f]{40}$ && "$artifact_digest" =~ ^[0-9a-f]{64}$ ]] \
    || { printf 'stage-backend-release: invalid controller identity\n' >&2; exit 65; }
  printf '{"deploymentId":"controller-%s","targetId":"controller","deployedSha":"%s","deployedTree":"%s","artifactDigest":"%s"}\n' \
    "$sha" "$sha" "$deployed_tree" "$artifact_digest" >"$stage/deployment-identity.json"
fi

# pnpm may hardlink package files to its content store. A later install can then make an
# already-published release writable through the shared inode, so detach every regular file
# before publication. Reflinks preserve the cheap-copy path without sharing inode metadata.
while IFS= read -r -d '' file; do
  detached="${file}.detached.$$"
  cp --preserve=mode,timestamps --reflink=auto -- "$file" "$detached"
  mv -f -- "$detached" "$file"
done < <(find "$stage" -type f -links +1 -print0)

# Legacy pnpm deploy creates this convenience link even though no runtime import uses it.
if [[ -n "$package_name" ]]; then
  self_link="$stage/$component/node_modules/.pnpm/node_modules/$package_name"
  [[ ! -L "$self_link" ]] || rm -f -- "$self_link"
fi

mkdir -p "$stage/bin"
cat >"$stage/bin/start" <<'SH'
#!/usr/bin/env bash
set -euo pipefail
root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)
identity=$(python3 - "$root/release.json" <<'PY'
import json
import re
import sys
try:
    with open(sys.argv[1], "r", encoding="utf-8") as stream:
        value = json.load(stream)
except (OSError, ValueError):
    raise SystemExit(1)
if not isinstance(value, dict):
    raise SystemExit(1)
component = value.get("component")
sha = value.get("target_sha")
allowed = {"collector", "controller", "botmaster-proxy", "actions-gateway"}
if value.get("schema") != 1 or component not in allowed or not isinstance(sha, str) or re.fullmatch(r"[0-9a-f]{40}", sha) is None:
    raise SystemExit(1)
print(f"{component}:{sha}")
PY
) || { printf 'backend-release: invalid release identity\n' >&2; exit 65; }
component=${identity%%:*}
sha=${identity#*:}
export OVERDECK_DEPLOY_SHA="$sha"
case "$component" in
  collector|controller)
    bun_bin=${OVERDECK_BUN_BIN:-}
    if [[ -z "$bun_bin" ]]; then
      bun_bin=$(command -v bun) || { printf 'backend-release: bun unavailable\n' >&2; exit 69; }
    fi
    if [[ "$component" == controller ]]; then
      [[ -f "$root/deployment-identity.json" && ! -L "$root/deployment-identity.json" ]] \
        || { printf 'backend-release: controller deployment identity unavailable\n' >&2; exit 65; }
      export OVERDECK_DEPLOYMENT_IDENTITY="$root/deployment-identity.json"
    fi
    exec "$bun_bin" run "$root/$component/src/index.ts"
    ;;
  botmaster-proxy)
    bun_bin=${OVERDECK_BUN_BIN:-}
    if [[ -z "$bun_bin" ]]; then
      bun_bin=$(command -v bun) || { printf 'backend-release: bun unavailable\n' >&2; exit 69; }
    fi
    exec "$bun_bin" run "$root/packaging/botmaster-proxy.ts"
    ;;
  actions-gateway)
    node_bin=${OVERDECK_NODE_BIN:-}
    if [[ -z "$node_bin" ]]; then
      node_bin=$(command -v node) || { printf 'backend-release: node unavailable\n' >&2; exit 69; }
    fi
    exec "$node_bin" "$root/actions-gateway/dist/src/server.js"
    ;;
esac
SH
chmod 0755 "$stage/bin/start"

OVERDECK_BACKEND_RELEASE_ROOT="$BACKEND_ROOT" \
  "$RELEASE_TOOL" publish "$component" "$sha" "$stage"
