name: Release

on:
  push:
    branches: [main]
    paths:
      - ".github/workflows/**"
      - "Cargo.toml"
      - "Cargo.lock"
      - "src/**"
      - "tests/**"
      - "scripts/**"
      - "extensions/**"
      - "docs/**"
      - "install.sh"
      - "lint.sh"
      - "test.sh"
      - "rho"
      - "rho-gondolin-run"
  workflow_dispatch:
    inputs:
      bump:
        description: Version bump to apply
        required: true
        default: patch
        type: choice
        options:
          - patch
          - minor
          - major
      force_version:
        description: Exact version to publish, overriding bump
        required: false
        type: string

permissions:
  contents: write

env:
  CARGO_TERM_COLOR: always
  RHO_BINS: rho

jobs:
  quality:
    name: Quality (${{ matrix.os }})
    strategy:
      fail-fast: false
      matrix:
        include:
          - os: ubuntu
            runner: namespace-profile-linux-medium
          - os: macos
            runner: namespace-profile-mac-medium
          - os: windows
            runner: namespace-profile-windows-medium
    runs-on: ${{ matrix.runner }}
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
        with:
          components: clippy, rustfmt
      - uses: Swatinem/rust-cache@v2
      - name: Format
        if: runner.os == 'Linux'
        run: cargo fmt --all -- --check
      - name: Lint
        run: cargo clippy --all-targets --all-features --locked -- -D warnings
      - name: Test
        run: cargo test --all-targets --all-features --locked
      - name: Build all binaries
        run: cargo build --all-targets --all-features --locked

  e2e:
    name: E2E tests
    needs: quality
    runs-on: namespace-profile-linux-medium
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2
      - run: ./test.sh

  bump:
    name: Bump version
    needs: e2e
    runs-on: namespace-profile-linux-medium
    outputs:
      version: ${{ steps.bump.outputs.version }}
    steps:
      - uses: actions/checkout@v4
        with:
          token: ${{ secrets.GITHUB_TOKEN }}
          fetch-depth: 0
      - uses: dtolnay/rust-toolchain@stable
      - name: Bump Cargo version
        id: bump
        env:
          BUMP: ${{ inputs.bump || 'patch' }}
          FORCE_VERSION: ${{ inputs.force_version }}
        run: |
          set -euo pipefail
          python3 - <<'PY'
          import os
          import re
          from pathlib import Path

          cargo = Path("Cargo.toml")
          text = cargo.read_text()
          match = re.search(r'(?m)^version = "([0-9]+)\.([0-9]+)\.([0-9]+)"$', text)
          if not match:
              raise SystemExit("could not find semver package version in Cargo.toml")

          force = os.environ.get("FORCE_VERSION", "").strip()
          if force:
              if not re.fullmatch(r"[0-9]+\.[0-9]+\.[0-9]+", force):
                  raise SystemExit("force_version must use MAJOR.MINOR.PATCH")
              new_version = force
          else:
              major, minor, patch = map(int, match.groups())
              bump = os.environ.get("BUMP", "patch")
              if bump == "major":
                  major += 1
                  minor = 0
                  patch = 0
              elif bump == "minor":
                  minor += 1
                  patch = 0
              elif bump == "patch":
                  patch += 1
              else:
                  raise SystemExit(f"unsupported bump: {bump}")
              new_version = f"{major}.{minor}.{patch}"

          text = text[:match.start()] + f'version = "{new_version}"' + text[match.end():]
          cargo.write_text(text)
          Path(os.environ["GITHUB_OUTPUT"]).write_text(f"version={new_version}\n")
          PY
          cargo update --workspace
      - name: Commit version bump
        env:
          VERSION: ${{ steps.bump.outputs.version }}
        run: |
          set -euo pipefail
          git config user.name "github-actions[bot]"
          git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
          git add Cargo.toml Cargo.lock
          git commit -m "chore: bump version to ${VERSION} [skip ci]"
          git push origin HEAD:main

  binaries:
    name: Binary (${{ matrix.target }})
    needs: bump
    strategy:
      fail-fast: false
      matrix:
        include:
          - runner: namespace-profile-linux-medium
            target: x86_64-unknown-linux-gnu
            archive: tar.gz
          - runner: namespace-profile-mac-medium
            target: x86_64-apple-darwin
            archive: tar.gz
          - runner: namespace-profile-mac-medium
            target: aarch64-apple-darwin
            archive: tar.gz
          - runner: namespace-profile-windows-medium
            target: x86_64-pc-windows-msvc
            archive: zip
    runs-on: ${{ matrix.runner }}
    steps:
      - uses: actions/checkout@v4
        with:
          ref: main
      - uses: dtolnay/rust-toolchain@stable
        with:
          targets: ${{ matrix.target }}
      - uses: Swatinem/rust-cache@v2
        with:
          key: ${{ matrix.target }}
      - name: Build release binary
        run: cargo build --release --locked --target "${{ matrix.target }}" --bin rho
      - name: Package Unix binaries
        if: matrix.archive == 'tar.gz'
        shell: bash
        run: |
          set -euo pipefail
          package="rho-${{ matrix.target }}"
          mkdir -p "dist/${package}"
          for bin in $RHO_BINS; do
            cp "target/${{ matrix.target }}/release/${bin}" "dist/${package}/${bin}"
          done
          cp install.sh "dist/${package}/install.sh"
          tar -C dist -czf "${package}.tar.gz" "${package}"
      - name: Package Windows binaries
        if: matrix.archive == 'zip'
        shell: pwsh
        run: |
          $package = "rho-${{ matrix.target }}"
          New-Item -ItemType Directory -Path "dist/$package" | Out-Null
          foreach ($bin in $env:RHO_BINS.Split(" ")) {
            Copy-Item "target/${{ matrix.target }}/release/$bin.exe" "dist/$package/$bin.exe"
          }
          Copy-Item "install.sh" "dist/$package/install.sh"
          Compress-Archive -Path "dist/$package" -DestinationPath "$package.zip"
      - uses: actions/upload-artifact@v4
        with:
          name: rho-${{ matrix.target }}
          path: rho-${{ matrix.target }}.*
          if-no-files-found: error

  publish:
    name: Publish crate
    needs: [bump, binaries]
    runs-on: namespace-profile-linux-medium
    steps:
      - uses: actions/checkout@v4
        with:
          ref: main
      - uses: dtolnay/rust-toolchain@stable
      - uses: Swatinem/rust-cache@v2
      - name: Verify package
        run: cargo publish --dry-run --locked
      - name: Publish to crates.io
        run: cargo publish --locked --token "${CARGO_REGISTRY_TOKEN}"
        env:
          CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}

  github-release:
    name: GitHub Release
    needs: [bump, publish]
    runs-on: namespace-profile-linux-medium
    steps:
      - uses: actions/checkout@v4
        with:
          ref: main
          fetch-depth: 0
      - uses: actions/download-artifact@v4
        with:
          path: artifacts
          merge-multiple: true
      - name: Tag and release
        env:
          GH_TOKEN: ${{ github.token }}
          VERSION: ${{ needs.bump.outputs.version }}
        run: |
          set -euo pipefail
          git tag "v${VERSION}"
          git push origin "v${VERSION}"
          gh release create "v${VERSION}" artifacts/* \
            --title "rho-cli v${VERSION}" \
            --notes "Published rho-cli ${VERSION} to crates.io and attached the cross-platform rho binary."
