name: Publish

on:
  release:
    types: [published]
  workflow_dispatch:
    inputs:
      tag:
        description: "Git tag to publish (e.g. v0.4.6)"
        required: true
        type: string
      dry_run:
        description: "Dry run — build everything but skip registry publishes"
        required: false
        default: false
        type: boolean
      force_republish:
        description: "Force publish even if version already exists"
        required: false
        default: false
        type: boolean
      targets:
        description: "Comma-separated targets (empty = all). Values: crates,cli,homebrew"
        required: false
        default: ""
        type: string
  repository_dispatch:
    types: [publish-release]

concurrency:
  group: publish-${{ github.event.inputs.tag || github.ref_name || github.run_id }}
  cancel-in-progress: false

permissions:
  contents: read

env:
  CARGO_TERM_COLOR: always

jobs:
  prepare:
    name: Prepare release
    runs-on: runner-medium
    outputs:
      tag: ${{ steps.meta.outputs.tag }}
      version: ${{ steps.meta.outputs.version }}
      checkout_ref: ${{ steps.meta.outputs.checkout_ref }}
      dry_run: ${{ steps.meta.outputs.dry_run }}
      force_republish: ${{ steps.meta.outputs.force_republish }}
      release_crates: ${{ steps.meta.outputs.release_crates }}
      release_cli: ${{ steps.meta.outputs.release_cli }}
      release_homebrew: ${{ steps.meta.outputs.release_homebrew }}
    steps:
      - uses: actions/checkout@v7
        with:
          fetch-depth: 0

      - name: Prepare release metadata
        id: meta
        uses: kreuzberg-dev/actions/prepare-release-metadata@v1
        with:
          available-targets: crates,cli,homebrew
          # Bootstrap alef from main (build from source) so this step doesn't
          # try to download a binary from the just-created release tag — those
          # binaries don't exist yet (they're built later in this same run).
          alef-version: main

  validate-versions:
    name: Validate versions
    needs: prepare
    runs-on: runner-medium
    steps:
      - uses: actions/checkout@v7
        with:
          ref: ${{ needs.prepare.outputs.checkout_ref }}

      - name: Validate manifest versions
        uses: kreuzberg-dev/actions/validate-versions@v1
        with:
          version: ${{ needs.prepare.outputs.version }}
          alef-version: main

  check-cratesio:
    name: Check crates.io
    needs: prepare
    if: needs.prepare.outputs.release_crates == 'true'
    runs-on: runner-medium
    outputs:
      exists: ${{ steps.check.outputs.exists }}
    steps:
      - name: Check crates.io for alef
        id: check
        uses: kreuzberg-dev/actions/check-registry@v1
        with:
          registry: cratesio
          package: alef
          version: ${{ needs.prepare.outputs.version }}
          alef-version: main

  check-github-release:
    name: Check GitHub release
    needs: prepare
    if: needs.prepare.outputs.release_cli == 'true'
    runs-on: runner-medium
    outputs:
      exists: ${{ steps.check.outputs.exists }}
    steps:
      - name: Check GitHub release assets
        id: check
        uses: kreuzberg-dev/actions/check-registry@v1
        with:
          registry: github-release
          package: alef
          version: ${{ needs.prepare.outputs.version }}
          tag: ${{ needs.prepare.outputs.tag }}
          asset-prefix: alef-
          repo: kreuzberg-dev/alef
          alef-version: main

  check-homebrew:
    name: Check Homebrew tap
    needs: prepare
    if: needs.prepare.outputs.release_homebrew == 'true'
    runs-on: runner-medium
    outputs:
      exists: ${{ steps.check.outputs.exists }}
    steps:
      - name: Check Homebrew formula
        id: check
        uses: kreuzberg-dev/actions/check-registry@v1
        with:
          registry: homebrew
          package: alef
          version: ${{ needs.prepare.outputs.version }}
          tap-repo: kreuzberg-dev/homebrew-tap
          alef-version: main

  build-cli:
    name: Build CLI (${{ matrix.label }})
    needs: [prepare, validate-versions, check-github-release]
    if: |
      always() &&
      needs.prepare.outputs.release_cli == 'true' &&
      needs.validate-versions.result == 'success' &&
      (needs.check-github-release.outputs.exists != 'true' || needs.prepare.outputs.force_republish == 'true')
    strategy:
      fail-fast: false
      matrix:
        include:
          - label: linux-x86_64
            runner: runner-medium
            target: x86_64-unknown-linux-gnu
          - label: linux-aarch64
            runner: runner-medium-arm64
            target: aarch64-unknown-linux-gnu
          - label: macos-arm64
            runner: macos-latest
            target: aarch64-apple-darwin
    runs-on: ${{ matrix.runner }}
    steps:
      - uses: actions/checkout@v7
        with:
          ref: ${{ needs.prepare.outputs.checkout_ref }}

      - name: Setup Rust
        uses: kreuzberg-dev/actions/setup-rust@v1
        with:
          target: ${{ matrix.target }}
          cache-key-prefix: publish-cli-${{ matrix.label }}
          install-llvm-cov: "false"
          macos-dynamic-lookup: "false"
          disable-cache: ${{ runner.os == 'Windows' && 'true' || 'false' }}

      - name: Build CLI
        id: build
        uses: kreuzberg-dev/actions/build-rust-cli@v1
        with:
          package-name: alef
          binary-name: alef
          target: ${{ matrix.target }}

      - name: Create release archive
        id: archive
        shell: bash
        run: |
          set -euo pipefail
          ARCHIVE_DIR="alef-${{ matrix.target }}"
          mkdir -p "${ARCHIVE_DIR}"
          cp "${{ steps.build.outputs.binary-path }}" "${ARCHIVE_DIR}/"
          if [[ "${{ runner.os }}" == "Windows" ]]; then
            ARCHIVE_NAME="alef-${{ matrix.target }}.zip"
            7z a "${ARCHIVE_NAME}" "${ARCHIVE_DIR}"
          else
            ARCHIVE_NAME="alef-${{ matrix.target }}.tar.gz"
            tar -czf "${ARCHIVE_NAME}" "${ARCHIVE_DIR}"
          fi
          echo "path=${ARCHIVE_NAME}" >> "$GITHUB_OUTPUT"
          echo "name=${ARCHIVE_NAME}" >> "$GITHUB_OUTPUT"

      - name: Upload CLI artifact
        uses: actions/upload-artifact@v7
        with:
          name: cli-${{ matrix.label }}
          path: ${{ steps.archive.outputs.path }}
          retention-days: 14

  publish-homebrew-formula:
    name: Update Homebrew formula
    needs: [prepare, validate-versions, check-homebrew, publish-crates]
    if: |
      always() &&
      needs.prepare.outputs.release_homebrew == 'true' &&
      needs.prepare.outputs.dry_run != 'true' &&
      needs.validate-versions.result == 'success' &&
      (needs.check-homebrew.outputs.exists != 'true' || needs.prepare.outputs.force_republish == 'true') &&
      needs.publish-crates.result != 'failure'
    runs-on: runner-medium
    steps:
      - uses: actions/create-github-app-token@v3
        id: tap-token
        with:
          app-id: ${{ secrets.BOT_APP_ID }}
          private-key: ${{ secrets.BOT_APP_PRIVATE_KEY }}
          owner: kreuzberg-dev
          repositories: homebrew-tap

      - uses: actions/checkout@v7
        with:
          ref: ${{ needs.prepare.outputs.checkout_ref }}

      - uses: actions/checkout@v7
        with:
          repository: kreuzberg-dev/homebrew-tap
          token: ${{ steps.tap-token.outputs.token }}
          path: homebrew-tap

      - name: Update formula
        env:
          TAG: ${{ needs.prepare.outputs.tag }}
          VERSION: ${{ needs.prepare.outputs.version }}
          TAP_DIR: ${{ github.workspace }}/homebrew-tap
        run: scripts/publish/update-homebrew-formula.sh

      - name: Commit and push formula
        working-directory: homebrew-tap
        env:
          GH_TOKEN: ${{ steps.tap-token.outputs.token }}
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
          if git diff --quiet Formula/alef.rb; then
            echo "No formula changes; skipping commit."
            exit 0
          fi
          git add Formula/alef.rb
          git commit -m "alef ${{ needs.prepare.outputs.version }}"
          git push origin HEAD

  homebrew-bottles:
    name: Build Homebrew bottle (${{ matrix.bottle_tag }})
    needs: [prepare, publish-homebrew-formula]
    if: |
      always() &&
      needs.prepare.outputs.release_homebrew == 'true' &&
      needs.prepare.outputs.dry_run != 'true' &&
      needs.publish-homebrew-formula.result == 'success'
    runs-on: ${{ matrix.os }}
    timeout-minutes: 90
    strategy:
      fail-fast: false
      matrix:
        include:
          - os: macos-latest
            bottle_tag: arm64_sequoia
            install_brew: false
          - os: macos-15-intel
            bottle_tag: sequoia
            install_brew: false
          - os: ubuntu-latest
            bottle_tag: x86_64_linux
            install_brew: true
          - os: ubuntu-24.04-arm
            bottle_tag: arm64_linux
            install_brew: true
    steps:
      - uses: actions/create-github-app-token@v3
        id: app-token
        with:
          app-id: ${{ secrets.BOT_APP_ID }}
          private-key: ${{ secrets.BOT_APP_PRIVATE_KEY }}
          owner: kreuzberg-dev

      - uses: actions/checkout@v7
        with:
          ref: ${{ needs.prepare.outputs.checkout_ref }}

      - name: Install Homebrew (Linux)
        if: ${{ matrix.install_brew }}
        uses: kreuzberg-dev/actions/install-homebrew-linux@v1

      - name: Build bottles
        uses: kreuzberg-dev/actions/homebrew-build-bottles@v1
        with:
          tag: ${{ needs.prepare.outputs.tag }}
          version: ${{ needs.prepare.outputs.version }}
          tap: kreuzberg-dev/homebrew-tap
          formulas: alef
          out-dir: ${{ github.workspace }}/bottle-json
          github-repo: kreuzberg-dev/alef
          token: ${{ steps.app-token.outputs.token }}

      - name: Upload bottle JSON manifests
        uses: actions/upload-artifact@v7
        with:
          name: homebrew-bottle-json-${{ matrix.bottle_tag }}
          path: ${{ github.workspace }}/bottle-json/*.json
          if-no-files-found: error
          retention-days: 14

  publish-homebrew-bottles:
    name: Merge Homebrew bottle DSL
    needs: [prepare, publish-homebrew-formula, homebrew-bottles]
    if: |
      always() &&
      needs.prepare.outputs.release_homebrew == 'true' &&
      needs.prepare.outputs.dry_run != 'true' &&
      needs.publish-homebrew-formula.result == 'success' &&
      needs.homebrew-bottles.result == 'success'
    runs-on: runner-medium
    steps:
      - uses: actions/create-github-app-token@v3
        id: tap-token
        with:
          app-id: ${{ secrets.BOT_APP_ID }}
          private-key: ${{ secrets.BOT_APP_PRIVATE_KEY }}
          owner: kreuzberg-dev
          repositories: homebrew-tap

      - uses: actions/checkout@v7
        with:
          ref: ${{ needs.prepare.outputs.checkout_ref }}

      - uses: actions/checkout@v7
        with:
          repository: kreuzberg-dev/homebrew-tap
          token: ${{ steps.tap-token.outputs.token }}
          path: homebrew-tap

      - uses: actions/download-artifact@v8
        with:
          pattern: homebrew-bottle-json-*
          path: ${{ github.workspace }}/bottle-json
          merge-multiple: true

      - name: Merge bottle DSL into formula
        uses: kreuzberg-dev/actions/homebrew-merge-bottles@v1
        with:
          tag: ${{ needs.prepare.outputs.tag }}
          version: ${{ needs.prepare.outputs.version }}
          tap-dir: ${{ github.workspace }}/homebrew-tap
          json-dir: ${{ github.workspace }}/bottle-json
          formulas: alef
          github-repo: kreuzberg-dev/alef

      - name: Commit and push bottle DSL
        working-directory: homebrew-tap
        env:
          GH_TOKEN: ${{ steps.tap-token.outputs.token }}
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
          if git diff --quiet Formula/alef.rb; then
            echo "No bottle DSL changes; skipping commit."
            exit 0
          fi
          git add Formula/alef.rb
          git commit -m "alef ${{ needs.prepare.outputs.version }}: add bottle DSL"
          git push origin HEAD

  upload-release-assets:
    name: Upload release assets
    needs: [prepare, validate-versions, check-github-release, build-cli]
    if: |
      always() &&
      needs.prepare.outputs.release_cli == 'true' &&
      needs.validate-versions.result == 'success' &&
      needs.build-cli.result == 'success' &&
      (needs.check-github-release.outputs.exists != 'true' || needs.prepare.outputs.force_republish == 'true')
    runs-on: runner-medium
    steps:
      - uses: actions/create-github-app-token@v3
        id: app-token
        with:
          app-id: ${{ secrets.BOT_APP_ID }}
          private-key: ${{ secrets.BOT_APP_PRIVATE_KEY }}
          owner: kreuzberg-dev

      - uses: actions/checkout@v7
        with:
          ref: ${{ needs.prepare.outputs.checkout_ref }}

      - name: Download all CLI artifacts
        uses: actions/download-artifact@v8
        with:
          pattern: cli-*
          path: dist/cli
          merge-multiple: true

      - name: Upload CLI artifacts to release
        uses: kreuzberg-dev/actions/publish-github-release@v1
        with:
          tag: ${{ needs.prepare.outputs.tag }}
          title: ${{ needs.prepare.outputs.tag }}
          artifacts: "dist/cli/*"
          generate-notes: "false"
          draft: "false"
          dry-run: ${{ needs.prepare.outputs.dry_run }}
          token: ${{ steps.app-token.outputs.token }}

  publish-crates:
    name: Publish crates.io
    needs: [prepare, validate-versions, check-cratesio]
    if: |
      always() &&
      needs.prepare.outputs.release_crates == 'true' &&
      needs.validate-versions.result == 'success' &&
      (needs.check-cratesio.outputs.exists != 'true' || needs.prepare.outputs.force_republish == 'true')
    runs-on: runner-medium
    permissions:
      contents: read
      id-token: write
    steps:
      - uses: actions/checkout@v7
        with:
          ref: ${{ needs.prepare.outputs.checkout_ref }}

      - name: Setup Rust
        uses: kreuzberg-dev/actions/setup-rust@v1
        with:
          install-llvm-cov: "false"

      - name: Publish to crates.io
        uses: kreuzberg-dev/actions/publish-crates@v1
        with:
          crates: alef
          version: ${{ needs.prepare.outputs.version }}
          dry-run: ${{ needs.prepare.outputs.dry_run }}

  finalize:
    name: Finalize release
    needs: [prepare, upload-release-assets, publish-crates, publish-homebrew-bottles]
    if: always()
    runs-on: runner-medium
    steps:
      - uses: actions/checkout@v7
        with:
          ref: ${{ needs.prepare.outputs.checkout_ref }}

      - name: Publish summary
        if: always()
        run: |
          {
            echo "## Release ${{ needs.prepare.outputs.tag }}"
            echo ""
            echo "| Registry | Status |"
            echo "|----------|--------|"
          } >> "$GITHUB_STEP_SUMMARY"
          report() {
            local name="$1" result="$2"
            case "$result" in
              success)  echo "| $name | :white_check_mark: Published |" ;;
              skipped)  echo "| $name | :fast_forward: Skipped |" ;;
              *)        echo "| $name | :x: $result |" ;;
            esac
          }
          {
            report "crates.io"       "${{ needs.publish-crates.result }}"
            report "GitHub Release"  "${{ needs.upload-release-assets.result }}"
            report "Homebrew"        "${{ needs.publish-homebrew-bottles.result }}"
          } >> "$GITHUB_STEP_SUMMARY"
