#!/usr/bin/env bash
set -euo pipefail

# release.sh — One-command version bump, build, and publish via Changesets.
#
# Usage:
#   ./scripts/release.sh patch                  # 0.2.0 → 0.2.1
#   ./scripts/release.sh minor                  # 0.2.0 → 0.3.0
#   ./scripts/release.sh major                  # 0.2.0 → 1.0.0
#   ./scripts/release.sh patch --dry-run        # everything except npm publish
#   ./scripts/release.sh patch --canary          # publish under @canary tag, no commit/tag
#   ./scripts/release.sh patch --canary --dry-run
#   ./scripts/release.sh --promote 0.2.8        # promote canary to @latest + commit/tag
#   ./scripts/release.sh --promote 0.2.8 --dry-run
#
# Steps (normal):
#   1. Preflight checks (clean tree, npm login)
#   2. Auto-create a changeset for all public packages
#   3. Run changeset version (bumps versions, generates CHANGELOGs)
#   4. Build all packages
#   5. Build CLI bundle (esbuild)
#   6. Publish to npm via changeset publish (unless --dry-run)
#   7. Commit and tag
#
# --canary: Steps 1-5 unchanged, Step 6 publishes with --tag canary, Step 7 skipped.
# --promote: Skips Steps 1-6, promotes canary to latest, then commits and tags.

REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
CLI_DIR="$REPO_ROOT/cli"

# ── Parse args ────────────────────────────────────────────────────────────────

dry_run=false
canary=false
promote=false
promote_version=""
bump_type=""

while [ $# -gt 0 ]; do
  case "$1" in
    --dry-run) dry_run=true ;;
    --canary) canary=true ;;
    --promote)
      promote=true
      shift
      if [ $# -eq 0 ] || [[ "$1" == --* ]]; then
        echo "Error: --promote requires a version argument (e.g. --promote 0.2.8)"
        exit 1
      fi
      promote_version="$1"
      ;;
    *) bump_type="$1" ;;
  esac
  shift
done

if [ "$promote" = true ] && [ "$canary" = true ]; then
  echo "Error: --canary and --promote cannot be used together"
  exit 1
fi

if [ "$promote" = false ]; then
  if [ -z "$bump_type" ]; then
    echo "Usage: $0 <patch|minor|major> [--dry-run] [--canary]"
    echo "       $0 --promote <version> [--dry-run]"
    exit 1
  fi

  if [[ ! "$bump_type" =~ ^(patch|minor|major)$ ]]; then
    echo "Error: bump type must be patch, minor, or major (got '$bump_type')"
    exit 1
  fi
fi

# ── Promote mode (skips Steps 1-6) ───────────────────────────────────────────

if [ "$promote" = true ]; then
  NEW_VERSION="$promote_version"
  echo ""
  echo "==> Promote mode: promoting v$NEW_VERSION from canary to latest..."

  # Get all publishable package names
  PACKAGES=$(node -e "
const { readFileSync } = require('fs');
const { resolve } = require('path');
const root = '$REPO_ROOT';
const dirs = ['packages/shared', 'packages/adapter-utils', 'packages/db',
  'packages/adapters/claude-local', 'packages/adapters/codex-local', 'packages/adapters/openclaw',
  'server', 'cli'];
const names = [];
for (const d of dirs) {
  try {
    const pkg = JSON.parse(readFileSync(resolve(root, d, 'package.json'), 'utf8'));
    if (!pkg.private) names.push(pkg.name);
  } catch {}
}
console.log(names.join('\n'));
")

  echo ""
  echo "  Promoting packages to @latest:"
  while IFS= read -r pkg; do
    if [ "$dry_run" = true ]; then
      echo "  [dry-run] npm dist-tag add ${pkg}@${NEW_VERSION} latest"
    else
      npm dist-tag add "${pkg}@${NEW_VERSION}" latest
      echo "  ✓ ${pkg}@${NEW_VERSION} → latest"
    fi
  done <<< "$PACKAGES"

  # Restore CLI dev package.json if present
  if [ -f "$CLI_DIR/package.dev.json" ]; then
    mv "$CLI_DIR/package.dev.json" "$CLI_DIR/package.json"
    echo "  ✓ Restored workspace dependencies in cli/package.json"
  fi

  # Remove the README copied for npm publishing
  if [ -f "$CLI_DIR/README.md" ]; then
    rm "$CLI_DIR/README.md"
  fi

  # Remove temporary build artifacts
  rm -rf "$REPO_ROOT/server/ui-dist"
  for pkg_dir in server packages/adapters/claude-local packages/adapters/codex-local; do
    rm -rf "$REPO_ROOT/$pkg_dir/skills"
  done

  # Stage release files, commit, and tag
  echo ""
  echo "  Committing and tagging v$NEW_VERSION..."
  if [ "$dry_run" = true ]; then
    echo "  [dry-run] git add + commit + tag v$NEW_VERSION"
  else
    git add \
      .changeset/ \
      '**/CHANGELOG.md' \
      '**/package.json' \
      cli/src/index.ts
    git commit -m "chore: release v$NEW_VERSION"
    git tag "v$NEW_VERSION"
    echo "  ✓ Committed and tagged v$NEW_VERSION"
  fi

  echo ""
  if [ "$dry_run" = true ]; then
    echo "Dry run complete for promote v$NEW_VERSION."
    echo "  - Would promote all packages to @latest"
    echo "  - Would commit and tag v$NEW_VERSION"
  else
    echo "Promoted all packages to @latest at v$NEW_VERSION"
    echo ""
    echo "Verify: npm view paperclipai@latest version"
    echo ""
    echo "To push:"
    echo "  git push && git push origin v$NEW_VERSION"
  fi
  exit 0
fi

# ── Step 1: Preflight checks ─────────────────────────────────────────────────

echo ""
echo "==> Step 1/7: Preflight checks..."

if [ "$dry_run" = false ]; then
  if ! npm whoami &>/dev/null; then
    echo "Error: Not logged in to npm. Run 'npm login' first."
    exit 1
  fi
  echo "  ✓ Logged in to npm as $(npm whoami)"
fi

if ! git -C "$REPO_ROOT" diff --quiet || ! git -C "$REPO_ROOT" diff --cached --quiet; then
  echo "Error: Working tree has uncommitted changes. Commit or stash them first."
  exit 1
fi
echo "  ✓ Working tree is clean"

# ── Step 2: Auto-create changeset ────────────────────────────────────────────

echo ""
echo "==> Step 2/7: Creating changeset ($bump_type bump for all packages)..."

# Get all publishable (non-private) package names
PACKAGES=$(node -e "
const { readdirSync, readFileSync } = require('fs');
const { resolve } = require('path');
const root = '$REPO_ROOT';
const wsYaml = readFileSync(resolve(root, 'pnpm-workspace.yaml'), 'utf8');
const dirs = ['packages/shared', 'packages/adapter-utils', 'packages/db',
  'packages/adapters/claude-local', 'packages/adapters/codex-local', 'packages/adapters/opencode-local', 'packages/adapters/openclaw',
  'server', 'cli'];
const names = [];
for (const d of dirs) {
  try {
    const pkg = JSON.parse(readFileSync(resolve(root, d, 'package.json'), 'utf8'));
    if (!pkg.private) names.push(pkg.name);
  } catch {}
}
console.log(names.join('\n'));
")

# Write a changeset file
CHANGESET_FILE="$REPO_ROOT/.changeset/release-bump.md"
{
  echo "---"
  while IFS= read -r pkg; do
    echo "\"$pkg\": $bump_type"
  done <<< "$PACKAGES"
  echo "---"
  echo ""
  echo "Version bump ($bump_type)"
} > "$CHANGESET_FILE"

echo "  ✓ Created changeset for $(echo "$PACKAGES" | wc -l | xargs) packages"

# ── Step 3: Version packages ─────────────────────────────────────────────────

echo ""
echo "==> Step 3/7: Running changeset version..."
cd "$REPO_ROOT"
npx changeset version
echo "  ✓ Versions bumped and CHANGELOGs generated"

# Read the new version from the CLI package
NEW_VERSION=$(node -e "console.log(require('$CLI_DIR/package.json').version)")
echo "  New version: $NEW_VERSION"

# Update the version string in cli/src/index.ts
CURRENT_VERSION_IN_SRC=$(sed -n 's/.*\.version("\([^"]*\)".*/\1/p' "$CLI_DIR/src/index.ts" | head -1)
if [ -n "$CURRENT_VERSION_IN_SRC" ] && [ "$CURRENT_VERSION_IN_SRC" != "$NEW_VERSION" ]; then
  sed -i '' "s/\.version(\"$CURRENT_VERSION_IN_SRC\")/\.version(\"$NEW_VERSION\")/" "$CLI_DIR/src/index.ts"
  echo "  ✓ Updated cli/src/index.ts version to $NEW_VERSION"
fi

# ── Step 4: Build packages ───────────────────────────────────────────────────

echo ""
echo "==> Step 4/7: Building all packages..."
cd "$REPO_ROOT"

# Build packages in dependency order (excluding CLI)
pnpm --filter @paperclipai/shared build
pnpm --filter @paperclipai/adapter-utils build
pnpm --filter @paperclipai/db build
pnpm --filter @paperclipai/adapter-claude-local build
pnpm --filter @paperclipai/adapter-codex-local build
pnpm --filter @paperclipai/adapter-opencode-local build
pnpm --filter @paperclipai/adapter-openclaw build
pnpm --filter @paperclipai/server build

# Build UI and bundle into server package for static serving
pnpm --filter @paperclipai/ui build
rm -rf "$REPO_ROOT/server/ui-dist"
cp -r "$REPO_ROOT/ui/dist" "$REPO_ROOT/server/ui-dist"

# Bundle skills into packages that need them (adapters + server)
for pkg_dir in server packages/adapters/claude-local packages/adapters/codex-local; do
  rm -rf "$REPO_ROOT/$pkg_dir/skills"
  cp -r "$REPO_ROOT/skills" "$REPO_ROOT/$pkg_dir/skills"
done
echo "  ✓ All packages built (including UI + skills)"

# ── Step 5: Build CLI bundle ─────────────────────────────────────────────────

echo ""
echo "==> Step 5/7: Building CLI bundle..."
cd "$REPO_ROOT"
"$REPO_ROOT/scripts/build-npm.sh" --skip-checks
echo "  ✓ CLI bundled"

# ── Step 6: Publish ──────────────────────────────────────────────────────────

if [ "$dry_run" = true ]; then
  echo ""
  if [ "$canary" = true ]; then
    echo "==> Step 6/7: Skipping publish (--dry-run, --canary)"
  else
    echo "==> Step 6/7: Skipping publish (--dry-run)"
  fi
  echo ""
  echo "  Preview what would be published:"
  for dir in packages/shared packages/adapter-utils packages/db \
             packages/adapters/claude-local packages/adapters/codex-local packages/adapters/opencode-local packages/adapters/openclaw \
             server cli; do
    echo "  --- $dir ---"
    cd "$REPO_ROOT/$dir"
    npm pack --dry-run 2>&1 | tail -3
  done
  cd "$REPO_ROOT"
  if [ "$canary" = true ]; then
    echo ""
    echo "  [dry-run] Would publish with: npx changeset publish --tag canary"
  fi
else
  echo ""
  if [ "$canary" = true ]; then
    echo "==> Step 6/7: Publishing to npm (canary)..."
    cd "$REPO_ROOT"
    npx changeset publish --tag canary
    echo "  ✓ Published all packages under @canary tag"
  else
    echo "==> Step 6/7: Publishing to npm..."
    cd "$REPO_ROOT"
    npx changeset publish
    echo "  ✓ Published all packages"
  fi
fi

# ── Step 7: Restore CLI dev package.json and commit ──────────────────────────

echo ""
if [ "$canary" = true ]; then
  echo "==> Step 7/7: Skipping commit and tag (canary mode — promote later)..."
else
  echo "==> Step 7/7: Restoring dev package.json, committing, and tagging..."
fi
cd "$REPO_ROOT"

# Restore the dev package.json (build-npm.sh backs it up)
if [ -f "$CLI_DIR/package.dev.json" ]; then
  mv "$CLI_DIR/package.dev.json" "$CLI_DIR/package.json"
  echo "  ✓ Restored workspace dependencies in cli/package.json"
fi

# Remove the README copied for npm publishing
if [ -f "$CLI_DIR/README.md" ]; then
  rm "$CLI_DIR/README.md"
fi

# Remove temporary build artifacts before committing (these are only needed during publish)
rm -rf "$REPO_ROOT/server/ui-dist"
for pkg_dir in server packages/adapters/claude-local packages/adapters/codex-local; do
  rm -rf "$REPO_ROOT/$pkg_dir/skills"
done

if [ "$canary" = false ]; then
  # Stage only release-related files (avoid sweeping unrelated changes with -A)
  git add \
    .changeset/ \
    '**/CHANGELOG.md' \
    '**/package.json' \
    cli/src/index.ts
  git commit -m "chore: release v$NEW_VERSION"
  git tag "v$NEW_VERSION"
  echo "  ✓ Committed and tagged v$NEW_VERSION"
fi

# ── Done ──────────────────────────────────────────────────────────────────────

echo ""
if [ "$canary" = true ]; then
  if [ "$dry_run" = true ]; then
    echo "Dry run complete for canary v$NEW_VERSION."
    echo "  - Versions bumped, built, and previewed"
    echo "  - Dev package.json restored"
    echo "  - No commit or tag (canary mode)"
    echo ""
    echo "To actually publish canary, run:"
    echo "  ./scripts/release.sh $bump_type --canary"
  else
    echo "Published canary at v$NEW_VERSION"
    echo ""
    echo "Verify: npm view paperclipai@canary version"
    echo ""
    echo "To promote to latest:"
    echo "  ./scripts/release.sh --promote $NEW_VERSION"
  fi
elif [ "$dry_run" = true ]; then
  echo "Dry run complete for v$NEW_VERSION."
  echo "  - Versions bumped, built, and previewed"
  echo "  - Dev package.json restored"
  echo "  - Commit and tag created (locally)"
  echo ""
  echo "To actually publish, run:"
  echo "  ./scripts/release.sh $bump_type"
else
  echo "Published all packages at v$NEW_VERSION"
  echo ""
  echo "To push:"
  echo "  git push && git push origin v$NEW_VERSION"
fi
