name: Verify Changesets

on:
  pull_request:
    types: [opened, synchronize, labeled, unlabeled, edited]
    branches:
      - main
      - next
      - 'release/**'
    paths:
      - '.changeset/**'
      - 'packages/**'

permissions: {}

jobs:
  verify:
    runs-on: ubuntu-latest
    if: >
      !startsWith(github.head_ref, 'changeset-release/') &&
      !(github.head_ref == 'next' && github.base_ref == 'main' && github.event.pull_request.head.repo.full_name == github.repository) &&
      !(github.head_ref == 'main' && github.base_ref == 'next' && github.event.pull_request.head.repo.full_name == github.repository) &&
      github.actor != 'github-actions[bot]' &&
      github.actor != 'better-release[bot]'
    permissions:
      contents: read
      pull-requests: read
    steps:
      - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
        with:
          fetch-depth: 0
          persist-credentials: false

      - name: Block pre.json on main
        if: github.base_ref == 'main'
        env:
          BASE_REF: ${{ github.base_ref }}
        run: |
          if git diff --diff-filter=d --name-only "origin/$BASE_REF"...HEAD | grep -q '^\.changeset/pre\.json$'; then
            echo "::error::.changeset/pre.json must not be committed to main. Pre-release mode belongs on the 'next' branch only."
            exit 1
          fi

      - name: Get changed changeset files
        id: changed
        env:
          BASE_REF: ${{ github.base_ref }}
        run: |
          FILES=$(git diff --diff-filter=dr --name-only "origin/$BASE_REF"...HEAD -- '.changeset/*.md' | grep -v 'README.md' || true)
          COUNT=$(echo "$FILES" | grep -c '.' || true)
          {
            echo "files<<EOF"
            echo "$FILES"
            echo "EOF"
            echo "count=$COUNT"
            echo "has_changesets=$( [ "$COUNT" -gt 0 ] && echo true || echo false )"
          } >> "$GITHUB_OUTPUT"

      - name: Check package changes require changeset
        if: steps.changed.outputs.has_changesets == 'false'
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          PR_NUMBER: ${{ github.event.pull_request.number }}
          BASE_REF: ${{ github.base_ref }}
        run: |
          LABELS=$(gh pr view "$PR_NUMBER" --json labels --jq '.labels[].name' 2>/dev/null || echo "")
          if echo "$LABELS" | grep -q "skip-changeset"; then
            echo "Skipping changeset requirement (skip-changeset label found)"
            exit 0
          fi

          PKG_CHANGES=$(git diff --name-only "origin/$BASE_REF"...HEAD -- 'packages/' | grep -c '.' || true)
          if [ "$PKG_CHANGES" -eq 0 ]; then
            echo "No package changes detected, changeset not required"
            exit 0
          fi

          echo "::error::Missing changeset. Add one with 'pnpm changeset' or add the 'skip-changeset' label."
          exit 1

      - name: Validate changeset files
        if: steps.changed.outputs.has_changesets == 'true'
        env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          PR_NUMBER: ${{ github.event.pull_request.number }}
          BASE_REF: ${{ github.base_ref }}
          CHANGESET_FILES: ${{ steps.changed.outputs.files }}
        run: |
          HAS_ERROR=0

          for f in $CHANGESET_FILES; do
            if [ -L "$f" ]; then
              echo "::error::Changeset $f is a symlink, which is not allowed."
              HAS_ERROR=1
              continue
            fi

            BASENAME=$(basename "$f")
            if ! echo "$BASENAME" | grep -qE '^[a-z][a-z0-9-]*\.md$'; then
              echo "::error::Changeset $f has an invalid filename. Use only lowercase letters, digits, and hyphens."
              HAS_ERROR=1
              continue
            fi

            if ! head -1 "$f" | grep -q '^---$'; then
              echo "::error::Changeset $f is missing YAML frontmatter opening fence."
              HAS_ERROR=1
              continue
            fi
            if ! sed -n '2,$ p' "$f" | grep -q '^---$'; then
              echo "::error::Changeset $f is missing YAML frontmatter closing fence."
              HAS_ERROR=1
              continue
            fi
            BODY=$(sed -n '2,/^---$/p' "$f" | head -n -1)
            while IFS= read -r line; do
              [ -z "$line" ] && continue
              if ! echo "$line" | grep -qE '^"[^"]+"\s*:\s*(major|minor|patch|none)\s*$'; then
                echo "::error::Changeset $f has invalid entry: $line — expected '\"package-name\": patch|minor|major|none'"
                HAS_ERROR=1
              fi
            done <<< "$BODY"
          done

          [ "$HAS_ERROR" -ne 0 ] && exit 1

          if [ "$BASE_REF" = "main" ] || echo "$BASE_REF" | grep -q '^release/'; then
            for f in $CHANGESET_FILES; do
              BUMPS=$(sed -n '/^---$/,/^---$/p' "$f" | grep -oE '(major|minor|patch)' | sort -u)
              if echo "$BUMPS" | grep -q "major"; then
                echo "::error::Changeset $f contains a 'major' bump. Only 'patch' bumps are allowed on '$BASE_REF'. PRs with minor/major bumps should target 'next' (auto-retarget may not have run yet)."
                exit 1
              fi
              if echo "$BUMPS" | grep -q "minor"; then
                echo "::error::Changeset $f contains a 'minor' bump. Only 'patch' bumps are allowed on '$BASE_REF'. PRs with minor/major bumps should target 'next' (auto-retarget may not have run yet)."
                exit 1
              fi
            done
            echo "All changesets are patch-only ($BASE_REF branch policy)"
          else
            echo "Target is $BASE_REF, all bump types allowed"
          fi
