#!/usr/bin/env bash
# docker-prep — pack @bytelyst/* tarballs for hermetic Docker builds.
#
# CANONICAL TEMPLATE — do not hand-edit copies in product repos.
# Sync via: bash learning_ai_common_plat/scripts/sync-docker-prep.sh
#
# Usage:
#   bash scripts/docker-prep.sh                # pack + rewrite (default)
#   bash scripts/docker-prep.sh --restore      # undo rewrite, drop .bak/tarballs
#   bash scripts/docker-prep.sh --dry-run      # list packs/rewrites, no side effects
#   bash scripts/docker-prep.sh --check        # exit 1 if rewrites still in place
#   bash scripts/docker-prep.sh --strip-overrides   # remove pnpm.overrides only
#   bash scripts/docker-prep.sh --force        # bypass idempotency guard
#   bash scripts/docker-prep.sh --keep         # do NOT auto-restore on error
#
# Behavior:
#   - Auto-discovers package.json files containing @bytelyst/* deps (max-depth 3).
#   - Idempotent: refuses to run pack mode if any *.bak exists (use --force).
#   - Safe: on error, auto-restores .bak files unless --keep is passed.
#   - Portable: sed -i '' for macOS, sed -i for Linux.
#
# Phase B refs: B1, B2, B5, B6, B7, B8 of docker-build-optimization-roadmap.md

set -uo pipefail

# ── CLI parsing ───────────────────────────────────────────────────
MODE=pack
FORCE=false
KEEP_ON_ERROR=false
DRY_RUN=false
while [ $# -gt 0 ]; do
  case "$1" in
    --restore) MODE=restore ;;
    --check) MODE=check ;;
    --strip-overrides) MODE=strip-overrides ;;
    --dry-run) DRY_RUN=true ;;
    --force) FORCE=true ;;
    --keep) KEEP_ON_ERROR=true ;;
    -h|--help) sed -n '2,21p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
    *) echo "✗ Unknown arg: $1" >&2; exit 2 ;;
  esac
  shift
done

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
REPO_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
COMMON_PLAT="${COMMON_PLAT_DIR:-${REPO_DIR}/../learning_ai_common_plat}"
TARBALL_DIR="${REPO_DIR}/.docker-deps"

# Portable sed -i (BSD vs GNU)
sed_inplace() {
  if [[ "$(uname)" == "Darwin" ]]; then
    sed -i '' "$@"
  else
    sed -i "$@"
  fi
}

# ── Discover package.json files with @bytelyst/* refs ────────────
discover_pkg_files() {
  find "$REPO_DIR" -maxdepth 3 -name 'package.json' \
    -not -path '*/node_modules/*' \
    -not -path '*/.docker-deps/*' \
    -not -path '*/dist/*' \
    -not -path '*/.next/*' 2>/dev/null \
    | while read -r f; do
        if grep -q '"@bytelyst/' "$f" 2>/dev/null; then
          echo "$f"
        fi
      done
}

# ── relative prefix from a package.json's dir back to repo root ──
rel_prefix_for() {
  local pkg_file="$1"
  local pkg_dir
  pkg_dir="$(dirname "$pkg_file")"
  # If package.json is at repo root, prefix is ./
  if [[ "$pkg_dir" == "$REPO_DIR" ]]; then
    echo ""
    return
  fi
  # Count dir depth from repo root → produce ../ * depth
  local rel="${pkg_dir#$REPO_DIR/}"
  local depth
  depth=$(echo "$rel" | awk -F/ '{print NF}')
  local prefix=""
  for ((i=0; i<depth; i++)); do prefix="${prefix}../"; done
  echo "$prefix"
}

# ── --check mode ─────────────────────────────────────────────────
if [[ "$MODE" == "check" ]]; then
  found=0
  for pkg in $(discover_pkg_files); do
    if grep -q '"file:.*\.docker-deps/' "$pkg"; then
      echo "✗ Rewritten refs still in $pkg"
      found=1
    fi
  done
  if [ -d "$TARBALL_DIR" ] && ls "$TARBALL_DIR"/*.tgz >/dev/null 2>&1; then
    echo "✗ Tarballs still in $TARBALL_DIR"
    found=1
  fi
  for bak in $(find "$REPO_DIR" -name 'package.json.bak' -not -path '*/node_modules/*' 2>/dev/null); do
    echo "✗ Backup still present: $bak"
    found=1
  done
  if [ $found -eq 0 ]; then
    echo "✓ docker-prep state clean"
    exit 0
  fi
  exit 1
fi

# ── --restore mode (idempotent) ──────────────────────────────────
restore_all() {
  local restored=0
  for bak in $(find "$REPO_DIR" -name 'package.json.bak' -not -path '*/node_modules/*' 2>/dev/null); do
    if $DRY_RUN; then
      echo "  [dry-run] would restore ${bak%.bak}"
    else
      mv "$bak" "${bak%.bak}"
      echo "  ✓ Restored ${bak%.bak}"
    fi
    restored=$((restored+1))
  done
  if [ -d "$TARBALL_DIR" ]; then
    if $DRY_RUN; then
      echo "  [dry-run] would clear tarballs from $TARBALL_DIR (preserving .gitkeep)"
    else
      # Preserve .gitkeep so the dir survives for fresh clones / Dockerfile COPY
      find "$TARBALL_DIR" -mindepth 1 ! -name '.gitkeep' -delete 2>/dev/null || true
      echo "  ✓ Cleared tarballs from $TARBALL_DIR (preserved .gitkeep)"
    fi
  fi
  if [ $restored -eq 0 ] && [ ! -d "$TARBALL_DIR" ]; then
    echo "  (nothing to restore — already clean)"
  fi
}

if [[ "$MODE" == "restore" ]]; then
  echo "=== docker-prep: restoring original state ==="
  restore_all
  echo "Done."
  exit 0
fi

# ── --strip-overrides (B8) ──────────────────────────────────────
if [[ "$MODE" == "strip-overrides" ]]; then
  echo "=== docker-prep: stripping pnpm.overrides ==="
  for pkg in $(discover_pkg_files); do
    if grep -q '"overrides"' "$pkg"; then
      if $DRY_RUN; then
        echo "  [dry-run] would strip overrides from $pkg"
      else
        PKG_FILE_ARG="$pkg" node "$SCRIPT_DIR/_docker-prep-strip.js"
        echo "  ✓ Stripped @bytelyst/* overrides from $pkg"
      fi
    fi
  done
  echo "Done."
  exit 0
fi

# ── Pack mode (default) ─────────────────────────────────────────
echo "=== docker-prep: packing @bytelyst/* tarballs ==="

# B2: idempotency guard
EXISTING_BAKS=$(find "$REPO_DIR" -name 'package.json.bak' -not -path '*/node_modules/*' 2>/dev/null | head -5)
if [[ -n "$EXISTING_BAKS" ]] && ! $FORCE; then
  echo "✗ Existing *.bak files detected — previous docker-prep run not cleaned up:"
  echo "$EXISTING_BAKS" | sed 's/^/    /'
  echo ""
  echo "Run one of:"
  echo "  bash scripts/docker-prep.sh --restore   # clean up"
  echo "  bash scripts/docker-prep.sh --force     # ignore guard (DANGEROUS)"
  exit 1
fi

# Verify common-plat sibling
if [ ! -d "$COMMON_PLAT/packages" ]; then
  echo "✗ Cannot find common-plat packages at $COMMON_PLAT/packages"
  echo "   Clone learning_ai_common_plat as a sibling directory, or set COMMON_PLAT_DIR."
  exit 1
fi

# B5: trap to auto-restore on error
cleanup_on_error() {
  local code=$?
  if [ $code -ne 0 ] && ! $KEEP_ON_ERROR && ! $DRY_RUN; then
    echo ""
    echo "⚠ docker-prep failed (exit $code) — auto-restoring (pass --keep to disable)..."
    restore_all
  fi
  return $code
}
trap cleanup_on_error EXIT

if $DRY_RUN; then
  echo "  [dry-run] would build @bytelyst/* packages in $COMMON_PLAT"
else
  echo "Building @bytelyst/* packages..."
  (cd "$COMMON_PLAT" && pnpm -r --filter './packages/*' build) >/dev/null
fi

# Pack each package
if $DRY_RUN; then
  echo "  [dry-run] would pack packages to $TARBALL_DIR"
else
  mkdir -p "$TARBALL_DIR"
  # Clear stale tarballs but preserve .gitkeep
  find "$TARBALL_DIR" -mindepth 1 ! -name '.gitkeep' -delete 2>/dev/null || true
fi

TARBALL_MAP_FILE=$(mktemp)
# Note: not adding to existing EXIT trap; clean up explicitly at end
for pkg_dir in "$COMMON_PLAT"/packages/*/; do
  pkg_name=$(node -p "require('${pkg_dir}package.json').name" 2>/dev/null || true)
  [[ -z "$pkg_name" ]] && continue
  if $DRY_RUN; then
    echo "  [dry-run] would pack $pkg_name"
    continue
  fi
  echo "  Packing $pkg_name..."
  tarball=$(cd "$pkg_dir" && pnpm pack --pack-destination "$TARBALL_DIR" 2>/dev/null | tail -1)
  filename=$(basename "$tarball")
  echo "${pkg_name}=${filename}" >> "$TARBALL_MAP_FILE"
  echo "    -> $filename"
done

if $DRY_RUN; then
  echo ""
  echo "  [dry-run] would rewrite refs in:"
  discover_pkg_files | sed 's/^/    /'
  rm -f "$TARBALL_MAP_FILE"
  trap - EXIT
  exit 0
fi

# ── Rewrite + inject overrides ───────────────────────────────────
echo ""
echo "Rewriting package.json @bytelyst/* refs to .docker-deps/ tarballs..."

PKG_FILES=$(discover_pkg_files)
if [[ -z "$PKG_FILES" ]]; then
  echo "✗ No package.json with @bytelyst/* deps found in $REPO_DIR"
  rm -f "$TARBALL_MAP_FILE"
  trap - EXIT
  exit 1
fi

for pkg_file in $PKG_FILES; do
  prefix="$(rel_prefix_for "$pkg_file")"
  cp "$pkg_file" "${pkg_file}.bak"
  tmp="${pkg_file}.tmp"
  cp "$pkg_file" "$tmp"
  while IFS='=' read -r pkg_name tarball; do
    [[ -z "$pkg_name" ]] && continue
    sed_inplace "s|\"${pkg_name}\": \"[^\"]*\"|\"${pkg_name}\": \"file:${prefix}.docker-deps/${tarball}\"|g" "$tmp"
  done < "$TARBALL_MAP_FILE"
  mv "$tmp" "$pkg_file"
  echo "  ✓ Rewrote $pkg_file"
done

# Inject overrides for transitive @bytelyst/* deps
echo ""
echo "Injecting pnpm.overrides..."
for pkg_file in $PKG_FILES; do
  prefix="$(rel_prefix_for "$pkg_file")"
  overrides=""
  while IFS='=' read -r pkg_name tarball; do
    [[ -z "$pkg_name" ]] && continue
    if [[ -n "$overrides" ]]; then overrides="$overrides, "; fi
    overrides="$overrides\"${pkg_name}\": \"file:${prefix}.docker-deps/${tarball}\""
  done < "$TARBALL_MAP_FILE"
  if [[ -n "$overrides" ]]; then
    PKG_FILE_ARG="$pkg_file" OVERRIDES_ARG="{${overrides}}" \
      node "$SCRIPT_DIR/_docker-prep-inject.js"
    echo "  ✓ Injected overrides into $pkg_file"
  fi
done

rm -f "$TARBALL_MAP_FILE"
trap - EXIT

echo ""
echo "✅ Done. Tarballs in $TARBALL_DIR"
echo ""
echo "Next:"
echo "  docker compose build                         # build images"
echo "  bash scripts/docker-prep.sh --restore        # undo when done"
echo "  bash scripts/docker-prep.sh --check          # verify clean state"
