name: release

on:
  push:
    tags:
      - 'v*'

env:
  CARGO_TERM_COLOR: always
  BINARY_NAME: oss-spec

# Default to no permissions; every job declares its own
# least-privilege `permissions:` block (see OSS_SPEC §10.3).
permissions: {}

jobs:
  update-repo:
    name: Update changelog and versions
    runs-on: ubuntu-latest
    permissions:
      contents: write
    outputs:
      commit_sha: ${{ steps.push.outputs.commit_sha }}
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
          ref: main

      - uses: dtolnay/rust-toolchain@1.88.0

      - name: Generate changelog
        run: scripts/generate-changelog.sh "${GITHUB_REF_NAME#v}"

      - name: Update versions
        run: scripts/update-versions.sh "${GITHUB_REF_NAME#v}"

      - name: Commit and push
        id: push
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git add -A
          if ! git diff --cached --quiet; then
            git commit -m "chore(release): update changelog and versions for ${GITHUB_REF_NAME}"
            git push origin main
          fi
          echo "commit_sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"

      - name: Update tag to new commit
        run: |
          git tag -f "${GITHUB_REF_NAME}" HEAD
          git push origin "${GITHUB_REF_NAME}" --force

      - name: Extract release notes
        id: notes
        run: |
          awk '/^## \[/{if(found) exit; found=1; next} found' CHANGELOG.md > release-notes.md
          cat release-notes.md

      - uses: actions/upload-artifact@v4
        with:
          name: release-notes
          path: release-notes.md
          retention-days: 1

  build:
    name: Build ${{ matrix.target }}
    needs: update-repo
    runs-on: ${{ matrix.os }}
    permissions:
      contents: read
    strategy:
      fail-fast: false
      matrix:
        include:
          - target: x86_64-unknown-linux-gnu
            os: ubuntu-latest
          - target: aarch64-unknown-linux-gnu
            os: ubuntu-latest
            use_cross: true
          - target: x86_64-apple-darwin
            os: macos-latest
          - target: aarch64-apple-darwin
            os: macos-latest
          - target: x86_64-pc-windows-msvc
            os: windows-latest
          - target: aarch64-pc-windows-msvc
            os: windows-latest

    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ needs.update-repo.outputs.commit_sha }}

      - uses: dtolnay/rust-toolchain@1.88.0
        with:
          targets: ${{ matrix.target }}

      - uses: Swatinem/rust-cache@v2
        with:
          key: ${{ matrix.target }}

      - name: Install cross
        if: matrix.use_cross == true
        uses: taiki-e/install-action@v2
        with:
          tool: cross

      - name: Build
        shell: bash
        run: |
          if [ "${{ matrix.use_cross }}" = "true" ]; then
            cross build --release --target ${{ matrix.target }}
          else
            cargo build --release --target ${{ matrix.target }}
          fi

      - name: Package (Unix)
        if: runner.os != 'Windows'
        shell: bash
        run: |
          VERSION="${GITHUB_REF_NAME#v}"
          TARGET="${{ matrix.target }}"
          STEM="${BINARY_NAME}-v${VERSION}-${TARGET}"
          mkdir -p dist staging
          cp "target/${TARGET}/release/${BINARY_NAME}" staging/
          cp README.md staging/ 2>/dev/null || true
          cp LICENSE* staging/ 2>/dev/null || true
          tar -czf "dist/${STEM}.tar.gz" -C staging .
          (cd dist && sha256sum "${STEM}.tar.gz" > "${STEM}.tar.gz.sha256")

      - name: Package (Windows)
        if: runner.os == 'Windows'
        shell: pwsh
        run: |
          $Version = "$env:GITHUB_REF_NAME".TrimStart('v')
          $Target = "${{ matrix.target }}"
          $Stem = "$env:BINARY_NAME-v${Version}-${Target}"
          New-Item -ItemType Directory -Force dist, staging | Out-Null
          Copy-Item "target/$Target/release/$env:BINARY_NAME.exe" staging/
          if (Test-Path README.md) { Copy-Item README.md staging/ }
          Get-ChildItem LICENSE* -ErrorAction SilentlyContinue | Copy-Item -Destination staging/
          Compress-Archive -Path staging/* -DestinationPath "dist/${Stem}.zip"
          $Hash = (Get-FileHash "dist/${Stem}.zip" -Algorithm SHA256).Hash.ToLower()
          "${Hash}  ${Stem}.zip" | Out-File -Encoding ascii "dist/${Stem}.zip.sha256"

      - uses: actions/upload-artifact@v4
        with:
          name: release-${{ matrix.target }}
          path: dist/
          retention-days: 1

  release:
    name: GitHub Release
    needs: [update-repo, build]
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: actions/download-artifact@v4
        with:
          path: artifacts
          merge-multiple: true

      - name: Prepare release body
        id: body
        run: |
          if [ -f artifacts/release-notes.md ] && [ -s artifacts/release-notes.md ]; then
            {
              echo 'RELEASE_BODY<<RELEASE_EOF'
              cat artifacts/release-notes.md
              echo 'RELEASE_EOF'
            } >> "$GITHUB_ENV"
            rm -f artifacts/release-notes.md
          else
            echo 'RELEASE_BODY=' >> "$GITHUB_ENV"
          fi

      - uses: softprops/action-gh-release@v2
        with:
          body: ${{ env.RELEASE_BODY }}
          generate_release_notes: ${{ env.RELEASE_BODY == '' }}
          files: artifacts/*

  sync-release-notes:
    name: Sync release notes
    needs: [update-repo, release]
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ needs.update-repo.outputs.commit_sha }}

      - name: Sync release notes from CHANGELOG
        run: scripts/sync-releases.sh "${GITHUB_REF_NAME#v}"
        env:
          GH_TOKEN: ${{ github.token }}

  publish-crates:
    name: Publish to crates.io
    needs: [update-repo, build]
    runs-on: ubuntu-latest
    environment: crates-registry
    # contents: read  — checkout only (job does not push).
    # id-token: write — OIDC-based trusted publishing to crates.io.
    permissions:
      contents: read
      id-token: write
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ needs.update-repo.outputs.commit_sha }}

      - uses: dtolnay/rust-toolchain@1.88.0

      - name: Authenticate to crates.io (trusted publishing)
        id: crates-auth
        uses: rust-lang/crates-io-auth-action@v1

      - name: Publish oss-spec
        run: cargo publish
        env:
          CARGO_REGISTRY_TOKEN: ${{ steps.crates-auth.outputs.token }}
