name: Publish to npm

permissions: {}

on:
  push:
    tags:
      - "v*"
  pull_request:
    types: [closed]
    branches: [main]
  workflow_dispatch:
    inputs:
      version:
        description: "Version to publish (e.g. 0.4.11). Tag v<version> must already exist."
        required: true
        type: string

jobs:
  publish:
    name: Publish
    runs-on: ubuntu-latest
    timeout-minutes: 10
    environment: npm-publish
    permissions:
      contents: write
      id-token: write
    # Run on tag push, manual dispatch, OR when a release/* PR is merged
    if: >-
      github.event_name == 'push' ||
      github.event_name == 'workflow_dispatch' ||
      (github.event.pull_request.merged == true &&
       startsWith(github.event.pull_request.head.ref, 'release/v'))
    steps:
      - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
        with:
          fetch-depth: 0
          # On manual dispatch, check out the existing tag so we publish the
          # exact commit that was tagged — not whatever is currently on main.
          ref: >-
            ${{ github.event_name == 'workflow_dispatch'
                && format('refs/tags/v{0}', inputs.version)
                || github.ref }}

      - name: Resolve version
        id: version
        env:
          EVENT_NAME: ${{ github.event_name }}
          INPUT_VERSION: ${{ inputs.version }}
          PR_HEAD_REF: ${{ github.event.pull_request.head.ref }}
        run: |
          if [ "$EVENT_NAME" = "push" ]; then
            VERSION="${GITHUB_REF_NAME#v}"
          elif [ "$EVENT_NAME" = "workflow_dispatch" ]; then
            VERSION="${INPUT_VERSION}"
            VERSION="${VERSION#v}"
          else
            BRANCH="${PR_HEAD_REF}"
            VERSION="${BRANCH#release/v}"
          fi
          echo "version=${VERSION}" >> "$GITHUB_OUTPUT"

          # Detect pre-release tag (e.g. 0.1.16-alpha.1 → alpha, 0.1.16-beta.2 → beta)
          if [[ "$VERSION" =~ -([a-zA-Z]+) ]]; then
            DIST_TAG="${BASH_REMATCH[1]}"
            echo "prerelease=true" >> "$GITHUB_OUTPUT"
          else
            DIST_TAG="latest"
            echo "prerelease=false" >> "$GITHUB_OUTPUT"
          fi
          echo "dist_tag=${DIST_TAG}" >> "$GITHUB_OUTPUT"
          echo "Resolved version=${VERSION} dist_tag=${DIST_TAG}"

      - name: Validate release channel
        env:
          VERSION: ${{ steps.version.outputs.version }}
          DIST_TAG: ${{ steps.version.outputs.dist_tag }}
          EVENT_NAME: ${{ github.event_name }}
          PR_HEAD_REF: ${{ github.event.pull_request.head.ref }}
        run: node scripts/validate-release-channel.mjs

      - name: Create release tag
        if: github.event_name == 'pull_request'
        env:
          VERSION: ${{ steps.version.outputs.version }}
        run: |
          git tag "v$VERSION"
          git push origin "v$VERSION"
      - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
      - uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
        with:
          node-version: 24
          registry-url: "https://registry.npmjs.org"
      - run: corepack enable
      - run: corepack prepare pnpm@10.17.1 --activate
      - run: bun install --frozen-lockfile
      - run: bun run build
      - run: bun run verify:packed-manifests

      - name: Publish packages
        env:
          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
          VERSION: ${{ steps.version.outputs.version }}
          DIST_TAG: ${{ steps.version.outputs.dist_tag }}
        run: |
          FAILED=0

          publish_pkg() {
            local filter="$1"
            local name="$2"

            # Check if this version is already published
            if npm view "${name}@${VERSION}" version >/dev/null 2>&1; then
              echo "⏭️  ${name}@${VERSION} already published — skipping"
              return 0
            fi

            echo "📦 Publishing ${name}@${VERSION}..."
            if pnpm --filter "$filter" publish --access public --no-git-checks --tag "$DIST_TAG"; then
              echo "✅ ${name}@${VERSION} published"
            else
              echo "❌ ${name}@${VERSION} failed to publish"
              FAILED=1
            fi
          }

          publish_pkg "@hyperframes/parsers" "@hyperframes/parsers"
          publish_pkg "@hyperframes/lint" "@hyperframes/lint"
          publish_pkg "@hyperframes/studio-server" "@hyperframes/studio-server"
          publish_pkg "@hyperframes/core" "@hyperframes/core"
          publish_pkg "@hyperframes/sdk" "@hyperframes/sdk"
          publish_pkg "@hyperframes/engine" "@hyperframes/engine"
          publish_pkg "@hyperframes/player" "@hyperframes/player"
          publish_pkg "@hyperframes/producer" "@hyperframes/producer"
          publish_pkg "@hyperframes/shader-transitions" "@hyperframes/shader-transitions"
          publish_pkg "@hyperframes/studio" "@hyperframes/studio"
          publish_pkg "@hyperframes/aws-lambda" "@hyperframes/aws-lambda"
          publish_pkg "@hyperframes/gcp-cloud-run" "@hyperframes/gcp-cloud-run"

          # CLI is @hyperframes/cli in the monorepo but published as unscoped "hyperframes" on npm.
          # Rewrite the name in package.json before publishing, then use npm publish directly
          # since pnpm --filter won't match the rewritten name.
          if npm view "hyperframes@${VERSION}" version >/dev/null 2>&1; then
            echo "⏭️  hyperframes@${VERSION} already published — skipping"
          else
            node -e "
              const fs = require('fs');
              const p = 'packages/cli/package.json';
              const pkg = JSON.parse(fs.readFileSync(p, 'utf8'));
              pkg.name = 'hyperframes';
              fs.writeFileSync(p, JSON.stringify(pkg, null, 2) + '\n');
            "
            echo "📦 Publishing hyperframes@${VERSION}..."
            if (cd packages/cli && npm publish --access public --tag "$DIST_TAG"); then
              echo "✅ hyperframes@${VERSION} published"
            else
              echo "❌ hyperframes@${VERSION} failed to publish"
              FAILED=1
            fi
          fi

          if [ "$FAILED" -ne 0 ]; then
            echo "::error::One or more packages failed to publish"
            exit 1
          fi

      - name: Create GitHub Release
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          VERSION: ${{ steps.version.outputs.version }}
          PRERELEASE: ${{ steps.version.outputs.prerelease }}
          REPO: ${{ github.repository }}
        run: |
          NOTES_FILE="releases/v${VERSION}.md"

          # Skip if release already exists (idempotent re-runs)
          if gh release view "v${VERSION}" --repo "$REPO" >/dev/null 2>&1; then
            echo "Release v${VERSION} already exists — skipping"
          else
            FLAGS=(--repo "$REPO" --title "v${VERSION}")
            if [ -f "$NOTES_FILE" ]; then
              FLAGS+=(--notes-file "$NOTES_FILE")
            else
              echo "No reviewed release notes found at $NOTES_FILE — using GitHub generated notes"
              FLAGS+=(--generate-notes)
            fi
            if [ "$PRERELEASE" = "true" ]; then
              FLAGS+=(--prerelease)
            fi
            gh release create "v${VERSION}" "${FLAGS[@]}"
          fi
